Fix buffer overflow in feature parsing
[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     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
784     TidyProgramName(cps->program, cps->host, cps->tidy);
785     cps->matchWins = 0;
786     ASSIGN(cps->variants, appData.variant);
787     cps->analysisSupport = 2; /* detect */
788     cps->analyzing = FALSE;
789     cps->initDone = FALSE;
790     cps->reload = FALSE;
791
792     /* New features added by Tord: */
793     cps->useFEN960 = FALSE;
794     cps->useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     cps->fenOverride  = appData.fenOverride[n];
797
798     /* [HGM] time odds: set factor for each machine */
799     cps->timeOdds  = appData.timeOdds[n];
800
801     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
802     cps->accumulateTC = appData.accumulateTC[n];
803     cps->maxNrOfSessions = 1;
804
805     /* [HGM] debug */
806     cps->debug = FALSE;
807
808     cps->supportsNPS = UNKNOWN;
809     cps->memSize = FALSE;
810     cps->maxCores = FALSE;
811     ASSIGN(cps->egtFormats, "");
812
813     /* [HGM] options */
814     cps->optionSettings  = appData.engOptions[n];
815
816     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
817     cps->isUCI = appData.isUCI[n]; /* [AS] */
818     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819
820     if (appData.protocolVersion[n] > PROTOVER
821         || appData.protocolVersion[n] < 1)
822       {
823         char buf[MSG_SIZ];
824         int len;
825
826         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
827                        appData.protocolVersion[n]);
828         if( (len >= MSG_SIZ) && appData.debugMode )
829           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830
831         DisplayFatalError(buf, 0, 2);
832       }
833     else
834       {
835         cps->protocolVersion = appData.protocolVersion[n];
836       }
837
838     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
839     ParseFeatures(appData.featureDefaults, cps);
840 }
841
842 ChessProgramState *savCps;
843
844 GameMode oldMode;
845
846 void
847 LoadEngine ()
848 {
849     int i;
850     if(WaitForEngine(savCps, LoadEngine)) return;
851     CommonEngineInit(); // recalculate time odds
852     if(gameInfo.variant != StringToVariant(appData.variant)) {
853         // we changed variant when loading the engine; this forces us to reset
854         Reset(TRUE, savCps != &first);
855         oldMode = BeginningOfGame; // to prevent restoring old mode
856     }
857     InitChessProgram(savCps, FALSE);
858     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
859     DisplayMessage("", "");
860     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
861     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
862     ThawUI();
863     SetGNUMode();
864     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
865 }
866
867 void
868 ReplaceEngine (ChessProgramState *cps, int n)
869 {
870     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
871     keepInfo = 1;
872     if(oldMode != BeginningOfGame) EditGameEvent();
873     keepInfo = 0;
874     UnloadEngine(cps);
875     appData.noChessProgram = FALSE;
876     appData.clockMode = TRUE;
877     InitEngine(cps, n);
878     UpdateLogos(TRUE);
879     if(n) return; // only startup first engine immediately; second can wait
880     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
881     LoadEngine();
882 }
883
884 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
885 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
886
887 static char resetOptions[] =
888         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
889         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
890         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
891         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
892
893 void
894 FloatToFront(char **list, char *engineLine)
895 {
896     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
897     int i=0;
898     if(appData.recentEngines <= 0) return;
899     TidyProgramName(engineLine, "localhost", tidy+1);
900     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
901     strncpy(buf+1, *list, MSG_SIZ-50);
902     if(p = strstr(buf, tidy)) { // tidy name appears in list
903         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
904         while(*p++ = *++q); // squeeze out
905     }
906     strcat(tidy, buf+1); // put list behind tidy name
907     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
908     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
909     ASSIGN(*list, tidy+1);
910 }
911
912 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
913
914 void
915 Load (ChessProgramState *cps, int i)
916 {
917     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
918     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
919         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
920         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
921         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
922         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
923         appData.firstProtocolVersion = PROTOVER;
924         ParseArgsFromString(buf);
925         SwapEngines(i);
926         ReplaceEngine(cps, i);
927         FloatToFront(&appData.recentEngineList, engineLine);
928         return;
929     }
930     p = engineName;
931     while(q = strchr(p, SLASH)) p = q+1;
932     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
933     if(engineDir[0] != NULLCHAR) {
934         ASSIGN(appData.directory[i], engineDir); p = engineName;
935     } else if(p != engineName) { // derive directory from engine path, when not given
936         p[-1] = 0;
937         ASSIGN(appData.directory[i], engineName);
938         p[-1] = SLASH;
939         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
940     } else { ASSIGN(appData.directory[i], "."); }
941     if(params[0]) {
942         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
943         snprintf(command, MSG_SIZ, "%s %s", p, params);
944         p = command;
945     }
946     ASSIGN(appData.chessProgram[i], p);
947     appData.isUCI[i] = isUCI;
948     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
949     appData.hasOwnBookUCI[i] = hasBook;
950     if(!nickName[0]) useNick = FALSE;
951     if(useNick) ASSIGN(appData.pgnName[i], nickName);
952     if(addToList) {
953         int len;
954         char quote;
955         q = firstChessProgramNames;
956         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
957         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
958         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
959                         quote, p, quote, appData.directory[i],
960                         useNick ? " -fn \"" : "",
961                         useNick ? nickName : "",
962                         useNick ? "\"" : "",
963                         v1 ? " -firstProtocolVersion 1" : "",
964                         hasBook ? "" : " -fNoOwnBookUCI",
965                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
966                         storeVariant ? " -variant " : "",
967                         storeVariant ? VariantName(gameInfo.variant) : "");
968         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
969         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
970         if(insert != q) insert[-1] = NULLCHAR;
971         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
972         if(q)   free(q);
973         FloatToFront(&appData.recentEngineList, buf);
974     }
975     ReplaceEngine(cps, i);
976 }
977
978 void
979 InitTimeControls ()
980 {
981     int matched, min, sec;
982     /*
983      * Parse timeControl resource
984      */
985     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
986                           appData.movesPerSession)) {
987         char buf[MSG_SIZ];
988         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
989         DisplayFatalError(buf, 0, 2);
990     }
991
992     /*
993      * Parse searchTime resource
994      */
995     if (*appData.searchTime != NULLCHAR) {
996         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
997         if (matched == 1) {
998             searchTime = min * 60;
999         } else if (matched == 2) {
1000             searchTime = min * 60 + sec;
1001         } else {
1002             char buf[MSG_SIZ];
1003             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1004             DisplayFatalError(buf, 0, 2);
1005         }
1006     }
1007 }
1008
1009 void
1010 InitBackEnd1 ()
1011 {
1012
1013     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1014     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1015
1016     GetTimeMark(&programStartTime);
1017     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1018     appData.seedBase = random() + (random()<<15);
1019     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1020
1021     ClearProgramStats();
1022     programStats.ok_to_send = 1;
1023     programStats.seen_stat = 0;
1024
1025     /*
1026      * Initialize game list
1027      */
1028     ListNew(&gameList);
1029
1030
1031     /*
1032      * Internet chess server status
1033      */
1034     if (appData.icsActive) {
1035         appData.matchMode = FALSE;
1036         appData.matchGames = 0;
1037 #if ZIPPY
1038         appData.noChessProgram = !appData.zippyPlay;
1039 #else
1040         appData.zippyPlay = FALSE;
1041         appData.zippyTalk = FALSE;
1042         appData.noChessProgram = TRUE;
1043 #endif
1044         if (*appData.icsHelper != NULLCHAR) {
1045             appData.useTelnet = TRUE;
1046             appData.telnetProgram = appData.icsHelper;
1047         }
1048     } else {
1049         appData.zippyTalk = appData.zippyPlay = FALSE;
1050     }
1051
1052     /* [AS] Initialize pv info list [HGM] and game state */
1053     {
1054         int i, j;
1055
1056         for( i=0; i<=framePtr; i++ ) {
1057             pvInfoList[i].depth = -1;
1058             boards[i][EP_STATUS] = EP_NONE;
1059             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1060         }
1061     }
1062
1063     InitTimeControls();
1064
1065     /* [AS] Adjudication threshold */
1066     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1067
1068     InitEngine(&first, 0);
1069     InitEngine(&second, 1);
1070     CommonEngineInit();
1071
1072     pairing.which = "pairing"; // pairing engine
1073     pairing.pr = NoProc;
1074     pairing.isr = NULL;
1075     pairing.program = appData.pairingEngine;
1076     pairing.host = "localhost";
1077     pairing.dir = ".";
1078
1079     if (appData.icsActive) {
1080         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1081     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1082         appData.clockMode = FALSE;
1083         first.sendTime = second.sendTime = 0;
1084     }
1085
1086 #if ZIPPY
1087     /* Override some settings from environment variables, for backward
1088        compatibility.  Unfortunately it's not feasible to have the env
1089        vars just set defaults, at least in xboard.  Ugh.
1090     */
1091     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1092       ZippyInit();
1093     }
1094 #endif
1095
1096     if (!appData.icsActive) {
1097       char buf[MSG_SIZ];
1098       int len;
1099
1100       /* Check for variants that are supported only in ICS mode,
1101          or not at all.  Some that are accepted here nevertheless
1102          have bugs; see comments below.
1103       */
1104       VariantClass variant = StringToVariant(appData.variant);
1105       switch (variant) {
1106       case VariantBughouse:     /* need four players and two boards */
1107       case VariantKriegspiel:   /* need to hide pieces and move details */
1108         /* case VariantFischeRandom: (Fabien: moved below) */
1109         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1110         if( (len >= MSG_SIZ) && appData.debugMode )
1111           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1112
1113         DisplayFatalError(buf, 0, 2);
1114         return;
1115
1116       case VariantUnknown:
1117       case VariantLoadable:
1118       case Variant29:
1119       case Variant30:
1120       case Variant31:
1121       case Variant32:
1122       case Variant33:
1123       case Variant34:
1124       case Variant35:
1125       case Variant36:
1126       default:
1127         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1128         if( (len >= MSG_SIZ) && appData.debugMode )
1129           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1130
1131         DisplayFatalError(buf, 0, 2);
1132         return;
1133
1134       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1135       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1136       case VariantGothic:     /* [HGM] should work */
1137       case VariantCapablanca: /* [HGM] should work */
1138       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1139       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1140       case VariantKnightmate: /* [HGM] should work */
1141       case VariantCylinder:   /* [HGM] untested */
1142       case VariantFalcon:     /* [HGM] untested */
1143       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1144                                  offboard interposition not understood */
1145       case VariantNormal:     /* definitely works! */
1146       case VariantWildCastle: /* pieces not automatically shuffled */
1147       case VariantNoCastle:   /* pieces not automatically shuffled */
1148       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1149       case VariantLosers:     /* should work except for win condition,
1150                                  and doesn't know captures are mandatory */
1151       case VariantSuicide:    /* should work except for win condition,
1152                                  and doesn't know captures are mandatory */
1153       case VariantGiveaway:   /* should work except for win condition,
1154                                  and doesn't know captures are mandatory */
1155       case VariantTwoKings:   /* should work */
1156       case VariantAtomic:     /* should work except for win condition */
1157       case Variant3Check:     /* should work except for win condition */
1158       case VariantShatranj:   /* should work except for all win conditions */
1159       case VariantMakruk:     /* should work except for draw countdown */
1160       case VariantBerolina:   /* might work if TestLegality is off */
1161       case VariantCapaRandom: /* should work */
1162       case VariantJanus:      /* should work */
1163       case VariantSuper:      /* experimental */
1164       case VariantGreat:      /* experimental, requires legality testing to be off */
1165       case VariantSChess:     /* S-Chess, should work */
1166       case VariantGrand:      /* should work */
1167       case VariantSpartan:    /* should work */
1168         break;
1169       }
1170     }
1171
1172 }
1173
1174 int
1175 NextIntegerFromString (char ** str, long * value)
1176 {
1177     int result = -1;
1178     char * s = *str;
1179
1180     while( *s == ' ' || *s == '\t' ) {
1181         s++;
1182     }
1183
1184     *value = 0;
1185
1186     if( *s >= '0' && *s <= '9' ) {
1187         while( *s >= '0' && *s <= '9' ) {
1188             *value = *value * 10 + (*s - '0');
1189             s++;
1190         }
1191
1192         result = 0;
1193     }
1194
1195     *str = s;
1196
1197     return result;
1198 }
1199
1200 int
1201 NextTimeControlFromString (char ** str, long * value)
1202 {
1203     long temp;
1204     int result = NextIntegerFromString( str, &temp );
1205
1206     if( result == 0 ) {
1207         *value = temp * 60; /* Minutes */
1208         if( **str == ':' ) {
1209             (*str)++;
1210             result = NextIntegerFromString( str, &temp );
1211             *value += temp; /* Seconds */
1212         }
1213     }
1214
1215     return result;
1216 }
1217
1218 int
1219 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1220 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1221     int result = -1, type = 0; long temp, temp2;
1222
1223     if(**str != ':') return -1; // old params remain in force!
1224     (*str)++;
1225     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1226     if( NextIntegerFromString( str, &temp ) ) return -1;
1227     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1228
1229     if(**str != '/') {
1230         /* time only: incremental or sudden-death time control */
1231         if(**str == '+') { /* increment follows; read it */
1232             (*str)++;
1233             if(**str == '!') type = *(*str)++; // Bronstein TC
1234             if(result = NextIntegerFromString( str, &temp2)) return -1;
1235             *inc = temp2 * 1000;
1236             if(**str == '.') { // read fraction of increment
1237                 char *start = ++(*str);
1238                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1239                 temp2 *= 1000;
1240                 while(start++ < *str) temp2 /= 10;
1241                 *inc += temp2;
1242             }
1243         } else *inc = 0;
1244         *moves = 0; *tc = temp * 1000; *incType = type;
1245         return 0;
1246     }
1247
1248     (*str)++; /* classical time control */
1249     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1250
1251     if(result == 0) {
1252         *moves = temp;
1253         *tc    = temp2 * 1000;
1254         *inc   = 0;
1255         *incType = type;
1256     }
1257     return result;
1258 }
1259
1260 int
1261 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1262 {   /* [HGM] get time to add from the multi-session time-control string */
1263     int incType, moves=1; /* kludge to force reading of first session */
1264     long time, increment;
1265     char *s = tcString;
1266
1267     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1268     do {
1269         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1270         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1271         if(movenr == -1) return time;    /* last move before new session     */
1272         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1273         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1274         if(!moves) return increment;     /* current session is incremental   */
1275         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1276     } while(movenr >= -1);               /* try again for next session       */
1277
1278     return 0; // no new time quota on this move
1279 }
1280
1281 int
1282 ParseTimeControl (char *tc, float ti, int mps)
1283 {
1284   long tc1;
1285   long tc2;
1286   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1287   int min, sec=0;
1288
1289   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1290   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1291       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1292   if(ti > 0) {
1293
1294     if(mps)
1295       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1296     else
1297       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1298   } else {
1299     if(mps)
1300       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1301     else
1302       snprintf(buf, MSG_SIZ, ":%s", mytc);
1303   }
1304   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1305
1306   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1307     return FALSE;
1308   }
1309
1310   if( *tc == '/' ) {
1311     /* Parse second time control */
1312     tc++;
1313
1314     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1315       return FALSE;
1316     }
1317
1318     if( tc2 == 0 ) {
1319       return FALSE;
1320     }
1321
1322     timeControl_2 = tc2 * 1000;
1323   }
1324   else {
1325     timeControl_2 = 0;
1326   }
1327
1328   if( tc1 == 0 ) {
1329     return FALSE;
1330   }
1331
1332   timeControl = tc1 * 1000;
1333
1334   if (ti >= 0) {
1335     timeIncrement = ti * 1000;  /* convert to ms */
1336     movesPerSession = 0;
1337   } else {
1338     timeIncrement = 0;
1339     movesPerSession = mps;
1340   }
1341   return TRUE;
1342 }
1343
1344 void
1345 InitBackEnd2 ()
1346 {
1347     if (appData.debugMode) {
1348 #    ifdef __GIT_VERSION
1349       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1350 #    else
1351       fprintf(debugFP, "Version: %s\n", programVersion);
1352 #    endif
1353     }
1354     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1355
1356     set_cont_sequence(appData.wrapContSeq);
1357     if (appData.matchGames > 0) {
1358         appData.matchMode = TRUE;
1359     } else if (appData.matchMode) {
1360         appData.matchGames = 1;
1361     }
1362     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1363         appData.matchGames = appData.sameColorGames;
1364     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1365         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1366         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1367     }
1368     Reset(TRUE, FALSE);
1369     if (appData.noChessProgram || first.protocolVersion == 1) {
1370       InitBackEnd3();
1371     } else {
1372       /* kludge: allow timeout for initial "feature" commands */
1373       FreezeUI();
1374       DisplayMessage("", _("Starting chess program"));
1375       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1376     }
1377 }
1378
1379 int
1380 CalculateIndex (int index, int gameNr)
1381 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1382     int res;
1383     if(index > 0) return index; // fixed nmber
1384     if(index == 0) return 1;
1385     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1386     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1387     return res;
1388 }
1389
1390 int
1391 LoadGameOrPosition (int gameNr)
1392 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1393     if (*appData.loadGameFile != NULLCHAR) {
1394         if (!LoadGameFromFile(appData.loadGameFile,
1395                 CalculateIndex(appData.loadGameIndex, gameNr),
1396                               appData.loadGameFile, FALSE)) {
1397             DisplayFatalError(_("Bad game file"), 0, 1);
1398             return 0;
1399         }
1400     } else if (*appData.loadPositionFile != NULLCHAR) {
1401         if (!LoadPositionFromFile(appData.loadPositionFile,
1402                 CalculateIndex(appData.loadPositionIndex, gameNr),
1403                                   appData.loadPositionFile)) {
1404             DisplayFatalError(_("Bad position file"), 0, 1);
1405             return 0;
1406         }
1407     }
1408     return 1;
1409 }
1410
1411 void
1412 ReserveGame (int gameNr, char resChar)
1413 {
1414     FILE *tf = fopen(appData.tourneyFile, "r+");
1415     char *p, *q, c, buf[MSG_SIZ];
1416     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1417     safeStrCpy(buf, lastMsg, MSG_SIZ);
1418     DisplayMessage(_("Pick new game"), "");
1419     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1420     ParseArgsFromFile(tf);
1421     p = q = appData.results;
1422     if(appData.debugMode) {
1423       char *r = appData.participants;
1424       fprintf(debugFP, "results = '%s'\n", p);
1425       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1426       fprintf(debugFP, "\n");
1427     }
1428     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1429     nextGame = q - p;
1430     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1431     safeStrCpy(q, p, strlen(p) + 2);
1432     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1433     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1434     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1435         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1436         q[nextGame] = '*';
1437     }
1438     fseek(tf, -(strlen(p)+4), SEEK_END);
1439     c = fgetc(tf);
1440     if(c != '"') // depending on DOS or Unix line endings we can be one off
1441          fseek(tf, -(strlen(p)+2), SEEK_END);
1442     else fseek(tf, -(strlen(p)+3), SEEK_END);
1443     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1444     DisplayMessage(buf, "");
1445     free(p); appData.results = q;
1446     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1447        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1448       int round = appData.defaultMatchGames * appData.tourneyType;
1449       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1450          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1451         UnloadEngine(&first);  // next game belongs to other pairing;
1452         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1453     }
1454     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1455 }
1456
1457 void
1458 MatchEvent (int mode)
1459 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1460         int dummy;
1461         if(matchMode) { // already in match mode: switch it off
1462             abortMatch = TRUE;
1463             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1464             return;
1465         }
1466 //      if(gameMode != BeginningOfGame) {
1467 //          DisplayError(_("You can only start a match from the initial position."), 0);
1468 //          return;
1469 //      }
1470         abortMatch = FALSE;
1471         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1472         /* Set up machine vs. machine match */
1473         nextGame = 0;
1474         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1475         if(appData.tourneyFile[0]) {
1476             ReserveGame(-1, 0);
1477             if(nextGame > appData.matchGames) {
1478                 char buf[MSG_SIZ];
1479                 if(strchr(appData.results, '*') == NULL) {
1480                     FILE *f;
1481                     appData.tourneyCycles++;
1482                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1483                         fclose(f);
1484                         NextTourneyGame(-1, &dummy);
1485                         ReserveGame(-1, 0);
1486                         if(nextGame <= appData.matchGames) {
1487                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1488                             matchMode = mode;
1489                             ScheduleDelayedEvent(NextMatchGame, 10000);
1490                             return;
1491                         }
1492                     }
1493                 }
1494                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1495                 DisplayError(buf, 0);
1496                 appData.tourneyFile[0] = 0;
1497                 return;
1498             }
1499         } else
1500         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1501             DisplayFatalError(_("Can't have a match with no chess programs"),
1502                               0, 2);
1503             return;
1504         }
1505         matchMode = mode;
1506         matchGame = roundNr = 1;
1507         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1508         NextMatchGame();
1509 }
1510
1511 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1512
1513 void
1514 InitBackEnd3 P((void))
1515 {
1516     GameMode initialMode;
1517     char buf[MSG_SIZ];
1518     int err, len;
1519
1520     InitChessProgram(&first, startedFromSetupPosition);
1521
1522     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1523         free(programVersion);
1524         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1525         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1526         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1527     }
1528
1529     if (appData.icsActive) {
1530 #ifdef WIN32
1531         /* [DM] Make a console window if needed [HGM] merged ifs */
1532         ConsoleCreate();
1533 #endif
1534         err = establish();
1535         if (err != 0)
1536           {
1537             if (*appData.icsCommPort != NULLCHAR)
1538               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1539                              appData.icsCommPort);
1540             else
1541               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1542                         appData.icsHost, appData.icsPort);
1543
1544             if( (len >= MSG_SIZ) && appData.debugMode )
1545               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1546
1547             DisplayFatalError(buf, err, 1);
1548             return;
1549         }
1550         SetICSMode();
1551         telnetISR =
1552           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1553         fromUserISR =
1554           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1555         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1556             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557     } else if (appData.noChessProgram) {
1558         SetNCPMode();
1559     } else {
1560         SetGNUMode();
1561     }
1562
1563     if (*appData.cmailGameName != NULLCHAR) {
1564         SetCmailMode();
1565         OpenLoopback(&cmailPR);
1566         cmailISR =
1567           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1568     }
1569
1570     ThawUI();
1571     DisplayMessage("", "");
1572     if (StrCaseCmp(appData.initialMode, "") == 0) {
1573       initialMode = BeginningOfGame;
1574       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1575         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1576         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1577         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1578         ModeHighlight();
1579       }
1580     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1581       initialMode = TwoMachinesPlay;
1582     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1583       initialMode = AnalyzeFile;
1584     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1585       initialMode = AnalyzeMode;
1586     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1587       initialMode = MachinePlaysWhite;
1588     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1589       initialMode = MachinePlaysBlack;
1590     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1591       initialMode = EditGame;
1592     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1593       initialMode = EditPosition;
1594     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1595       initialMode = Training;
1596     } else {
1597       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1598       if( (len >= MSG_SIZ) && appData.debugMode )
1599         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1600
1601       DisplayFatalError(buf, 0, 2);
1602       return;
1603     }
1604
1605     if (appData.matchMode) {
1606         if(appData.tourneyFile[0]) { // start tourney from command line
1607             FILE *f;
1608             if(f = fopen(appData.tourneyFile, "r")) {
1609                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1610                 fclose(f);
1611                 appData.clockMode = TRUE;
1612                 SetGNUMode();
1613             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1614         }
1615         MatchEvent(TRUE);
1616     } else if (*appData.cmailGameName != NULLCHAR) {
1617         /* Set up cmail mode */
1618         ReloadCmailMsgEvent(TRUE);
1619     } else {
1620         /* Set up other modes */
1621         if (initialMode == AnalyzeFile) {
1622           if (*appData.loadGameFile == NULLCHAR) {
1623             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1624             return;
1625           }
1626         }
1627         if (*appData.loadGameFile != NULLCHAR) {
1628             (void) LoadGameFromFile(appData.loadGameFile,
1629                                     appData.loadGameIndex,
1630                                     appData.loadGameFile, TRUE);
1631         } else if (*appData.loadPositionFile != NULLCHAR) {
1632             (void) LoadPositionFromFile(appData.loadPositionFile,
1633                                         appData.loadPositionIndex,
1634                                         appData.loadPositionFile);
1635             /* [HGM] try to make self-starting even after FEN load */
1636             /* to allow automatic setup of fairy variants with wtm */
1637             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1638                 gameMode = BeginningOfGame;
1639                 setboardSpoiledMachineBlack = 1;
1640             }
1641             /* [HGM] loadPos: make that every new game uses the setup */
1642             /* from file as long as we do not switch variant          */
1643             if(!blackPlaysFirst) {
1644                 startedFromPositionFile = TRUE;
1645                 CopyBoard(filePosition, boards[0]);
1646             }
1647         }
1648         if (initialMode == AnalyzeMode) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1651             return;
1652           }
1653           if (appData.icsActive) {
1654             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1655             return;
1656           }
1657           AnalyzeModeEvent();
1658         } else if (initialMode == AnalyzeFile) {
1659           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1660           ShowThinkingEvent();
1661           AnalyzeFileEvent();
1662           AnalysisPeriodicEvent(1);
1663         } else if (initialMode == MachinePlaysWhite) {
1664           if (appData.noChessProgram) {
1665             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1666                               0, 2);
1667             return;
1668           }
1669           if (appData.icsActive) {
1670             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1671                               0, 2);
1672             return;
1673           }
1674           MachineWhiteEvent();
1675         } else if (initialMode == MachinePlaysBlack) {
1676           if (appData.noChessProgram) {
1677             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1678                               0, 2);
1679             return;
1680           }
1681           if (appData.icsActive) {
1682             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1683                               0, 2);
1684             return;
1685           }
1686           MachineBlackEvent();
1687         } else if (initialMode == TwoMachinesPlay) {
1688           if (appData.noChessProgram) {
1689             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1690                               0, 2);
1691             return;
1692           }
1693           if (appData.icsActive) {
1694             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1695                               0, 2);
1696             return;
1697           }
1698           TwoMachinesEvent();
1699         } else if (initialMode == EditGame) {
1700           EditGameEvent();
1701         } else if (initialMode == EditPosition) {
1702           EditPositionEvent();
1703         } else if (initialMode == Training) {
1704           if (*appData.loadGameFile == NULLCHAR) {
1705             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1706             return;
1707           }
1708           TrainingEvent();
1709         }
1710     }
1711 }
1712
1713 void
1714 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1715 {
1716     DisplayBook(current+1);
1717
1718     MoveHistorySet( movelist, first, last, current, pvInfoList );
1719
1720     EvalGraphSet( first, last, current, pvInfoList );
1721
1722     MakeEngineOutputTitle();
1723 }
1724
1725 /*
1726  * Establish will establish a contact to a remote host.port.
1727  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1728  *  used to talk to the host.
1729  * Returns 0 if okay, error code if not.
1730  */
1731 int
1732 establish ()
1733 {
1734     char buf[MSG_SIZ];
1735
1736     if (*appData.icsCommPort != NULLCHAR) {
1737         /* Talk to the host through a serial comm port */
1738         return OpenCommPort(appData.icsCommPort, &icsPR);
1739
1740     } else if (*appData.gateway != NULLCHAR) {
1741         if (*appData.remoteShell == NULLCHAR) {
1742             /* Use the rcmd protocol to run telnet program on a gateway host */
1743             snprintf(buf, sizeof(buf), "%s %s %s",
1744                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1745             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1746
1747         } else {
1748             /* Use the rsh program to run telnet program on a gateway host */
1749             if (*appData.remoteUser == NULLCHAR) {
1750                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1751                         appData.gateway, appData.telnetProgram,
1752                         appData.icsHost, appData.icsPort);
1753             } else {
1754                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1755                         appData.remoteShell, appData.gateway,
1756                         appData.remoteUser, appData.telnetProgram,
1757                         appData.icsHost, appData.icsPort);
1758             }
1759             return StartChildProcess(buf, "", &icsPR);
1760
1761         }
1762     } else if (appData.useTelnet) {
1763         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1764
1765     } else {
1766         /* TCP socket interface differs somewhat between
1767            Unix and NT; handle details in the front end.
1768            */
1769         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1770     }
1771 }
1772
1773 void
1774 EscapeExpand (char *p, char *q)
1775 {       // [HGM] initstring: routine to shape up string arguments
1776         while(*p++ = *q++) if(p[-1] == '\\')
1777             switch(*q++) {
1778                 case 'n': p[-1] = '\n'; break;
1779                 case 'r': p[-1] = '\r'; break;
1780                 case 't': p[-1] = '\t'; break;
1781                 case '\\': p[-1] = '\\'; break;
1782                 case 0: *p = 0; return;
1783                 default: p[-1] = q[-1]; break;
1784             }
1785 }
1786
1787 void
1788 show_bytes (FILE *fp, char *buf, int count)
1789 {
1790     while (count--) {
1791         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1792             fprintf(fp, "\\%03o", *buf & 0xff);
1793         } else {
1794             putc(*buf, fp);
1795         }
1796         buf++;
1797     }
1798     fflush(fp);
1799 }
1800
1801 /* Returns an errno value */
1802 int
1803 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1804 {
1805     char buf[8192], *p, *q, *buflim;
1806     int left, newcount, outcount;
1807
1808     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1809         *appData.gateway != NULLCHAR) {
1810         if (appData.debugMode) {
1811             fprintf(debugFP, ">ICS: ");
1812             show_bytes(debugFP, message, count);
1813             fprintf(debugFP, "\n");
1814         }
1815         return OutputToProcess(pr, message, count, outError);
1816     }
1817
1818     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1819     p = message;
1820     q = buf;
1821     left = count;
1822     newcount = 0;
1823     while (left) {
1824         if (q >= buflim) {
1825             if (appData.debugMode) {
1826                 fprintf(debugFP, ">ICS: ");
1827                 show_bytes(debugFP, buf, newcount);
1828                 fprintf(debugFP, "\n");
1829             }
1830             outcount = OutputToProcess(pr, buf, newcount, outError);
1831             if (outcount < newcount) return -1; /* to be sure */
1832             q = buf;
1833             newcount = 0;
1834         }
1835         if (*p == '\n') {
1836             *q++ = '\r';
1837             newcount++;
1838         } else if (((unsigned char) *p) == TN_IAC) {
1839             *q++ = (char) TN_IAC;
1840             newcount ++;
1841         }
1842         *q++ = *p++;
1843         newcount++;
1844         left--;
1845     }
1846     if (appData.debugMode) {
1847         fprintf(debugFP, ">ICS: ");
1848         show_bytes(debugFP, buf, newcount);
1849         fprintf(debugFP, "\n");
1850     }
1851     outcount = OutputToProcess(pr, buf, newcount, outError);
1852     if (outcount < newcount) return -1; /* to be sure */
1853     return count;
1854 }
1855
1856 void
1857 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1858 {
1859     int outError, outCount;
1860     static int gotEof = 0;
1861     static FILE *ini;
1862
1863     /* Pass data read from player on to ICS */
1864     if (count > 0) {
1865         gotEof = 0;
1866         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1867         if (outCount < count) {
1868             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1869         }
1870         if(have_sent_ICS_logon == 2) {
1871           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1872             fprintf(ini, "%s", message);
1873             have_sent_ICS_logon = 3;
1874           } else
1875             have_sent_ICS_logon = 1;
1876         } else if(have_sent_ICS_logon == 3) {
1877             fprintf(ini, "%s", message);
1878             fclose(ini);
1879           have_sent_ICS_logon = 1;
1880         }
1881     } else if (count < 0) {
1882         RemoveInputSource(isr);
1883         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1884     } else if (gotEof++ > 0) {
1885         RemoveInputSource(isr);
1886         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1887     }
1888 }
1889
1890 void
1891 KeepAlive ()
1892 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1893     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1894     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1895     SendToICS("date\n");
1896     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1897 }
1898
1899 /* added routine for printf style output to ics */
1900 void
1901 ics_printf (char *format, ...)
1902 {
1903     char buffer[MSG_SIZ];
1904     va_list args;
1905
1906     va_start(args, format);
1907     vsnprintf(buffer, sizeof(buffer), format, args);
1908     buffer[sizeof(buffer)-1] = '\0';
1909     SendToICS(buffer);
1910     va_end(args);
1911 }
1912
1913 void
1914 SendToICS (char *s)
1915 {
1916     int count, outCount, outError;
1917
1918     if (icsPR == NoProc) return;
1919
1920     count = strlen(s);
1921     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1922     if (outCount < count) {
1923         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1924     }
1925 }
1926
1927 /* This is used for sending logon scripts to the ICS. Sending
1928    without a delay causes problems when using timestamp on ICC
1929    (at least on my machine). */
1930 void
1931 SendToICSDelayed (char *s, long msdelay)
1932 {
1933     int count, outCount, outError;
1934
1935     if (icsPR == NoProc) return;
1936
1937     count = strlen(s);
1938     if (appData.debugMode) {
1939         fprintf(debugFP, ">ICS: ");
1940         show_bytes(debugFP, s, count);
1941         fprintf(debugFP, "\n");
1942     }
1943     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1944                                       msdelay);
1945     if (outCount < count) {
1946         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1947     }
1948 }
1949
1950
1951 /* Remove all highlighting escape sequences in s
1952    Also deletes any suffix starting with '('
1953    */
1954 char *
1955 StripHighlightAndTitle (char *s)
1956 {
1957     static char retbuf[MSG_SIZ];
1958     char *p = retbuf;
1959
1960     while (*s != NULLCHAR) {
1961         while (*s == '\033') {
1962             while (*s != NULLCHAR && !isalpha(*s)) s++;
1963             if (*s != NULLCHAR) s++;
1964         }
1965         while (*s != NULLCHAR && *s != '\033') {
1966             if (*s == '(' || *s == '[') {
1967                 *p = NULLCHAR;
1968                 return retbuf;
1969             }
1970             *p++ = *s++;
1971         }
1972     }
1973     *p = NULLCHAR;
1974     return retbuf;
1975 }
1976
1977 /* Remove all highlighting escape sequences in s */
1978 char *
1979 StripHighlight (char *s)
1980 {
1981     static char retbuf[MSG_SIZ];
1982     char *p = retbuf;
1983
1984     while (*s != NULLCHAR) {
1985         while (*s == '\033') {
1986             while (*s != NULLCHAR && !isalpha(*s)) s++;
1987             if (*s != NULLCHAR) s++;
1988         }
1989         while (*s != NULLCHAR && *s != '\033') {
1990             *p++ = *s++;
1991         }
1992     }
1993     *p = NULLCHAR;
1994     return retbuf;
1995 }
1996
1997 char *variantNames[] = VARIANT_NAMES;
1998 char *
1999 VariantName (VariantClass v)
2000 {
2001     return variantNames[v];
2002 }
2003
2004
2005 /* Identify a variant from the strings the chess servers use or the
2006    PGN Variant tag names we use. */
2007 VariantClass
2008 StringToVariant (char *e)
2009 {
2010     char *p;
2011     int wnum = -1;
2012     VariantClass v = VariantNormal;
2013     int i, found = FALSE;
2014     char buf[MSG_SIZ];
2015     int len;
2016
2017     if (!e) return v;
2018
2019     /* [HGM] skip over optional board-size prefixes */
2020     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2021         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2022         while( *e++ != '_');
2023     }
2024
2025     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2026         v = VariantNormal;
2027         found = TRUE;
2028     } else
2029     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2030       if (StrCaseStr(e, variantNames[i])) {
2031         v = (VariantClass) i;
2032         found = TRUE;
2033         break;
2034       }
2035     }
2036
2037     if (!found) {
2038       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2039           || StrCaseStr(e, "wild/fr")
2040           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2041         v = VariantFischeRandom;
2042       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2043                  (i = 1, p = StrCaseStr(e, "w"))) {
2044         p += i;
2045         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2046         if (isdigit(*p)) {
2047           wnum = atoi(p);
2048         } else {
2049           wnum = -1;
2050         }
2051         switch (wnum) {
2052         case 0: /* FICS only, actually */
2053         case 1:
2054           /* Castling legal even if K starts on d-file */
2055           v = VariantWildCastle;
2056           break;
2057         case 2:
2058         case 3:
2059         case 4:
2060           /* Castling illegal even if K & R happen to start in
2061              normal positions. */
2062           v = VariantNoCastle;
2063           break;
2064         case 5:
2065         case 7:
2066         case 8:
2067         case 10:
2068         case 11:
2069         case 12:
2070         case 13:
2071         case 14:
2072         case 15:
2073         case 18:
2074         case 19:
2075           /* Castling legal iff K & R start in normal positions */
2076           v = VariantNormal;
2077           break;
2078         case 6:
2079         case 20:
2080         case 21:
2081           /* Special wilds for position setup; unclear what to do here */
2082           v = VariantLoadable;
2083           break;
2084         case 9:
2085           /* Bizarre ICC game */
2086           v = VariantTwoKings;
2087           break;
2088         case 16:
2089           v = VariantKriegspiel;
2090           break;
2091         case 17:
2092           v = VariantLosers;
2093           break;
2094         case 22:
2095           v = VariantFischeRandom;
2096           break;
2097         case 23:
2098           v = VariantCrazyhouse;
2099           break;
2100         case 24:
2101           v = VariantBughouse;
2102           break;
2103         case 25:
2104           v = Variant3Check;
2105           break;
2106         case 26:
2107           /* Not quite the same as FICS suicide! */
2108           v = VariantGiveaway;
2109           break;
2110         case 27:
2111           v = VariantAtomic;
2112           break;
2113         case 28:
2114           v = VariantShatranj;
2115           break;
2116
2117         /* Temporary names for future ICC types.  The name *will* change in
2118            the next xboard/WinBoard release after ICC defines it. */
2119         case 29:
2120           v = Variant29;
2121           break;
2122         case 30:
2123           v = Variant30;
2124           break;
2125         case 31:
2126           v = Variant31;
2127           break;
2128         case 32:
2129           v = Variant32;
2130           break;
2131         case 33:
2132           v = Variant33;
2133           break;
2134         case 34:
2135           v = Variant34;
2136           break;
2137         case 35:
2138           v = Variant35;
2139           break;
2140         case 36:
2141           v = Variant36;
2142           break;
2143         case 37:
2144           v = VariantShogi;
2145           break;
2146         case 38:
2147           v = VariantXiangqi;
2148           break;
2149         case 39:
2150           v = VariantCourier;
2151           break;
2152         case 40:
2153           v = VariantGothic;
2154           break;
2155         case 41:
2156           v = VariantCapablanca;
2157           break;
2158         case 42:
2159           v = VariantKnightmate;
2160           break;
2161         case 43:
2162           v = VariantFairy;
2163           break;
2164         case 44:
2165           v = VariantCylinder;
2166           break;
2167         case 45:
2168           v = VariantFalcon;
2169           break;
2170         case 46:
2171           v = VariantCapaRandom;
2172           break;
2173         case 47:
2174           v = VariantBerolina;
2175           break;
2176         case 48:
2177           v = VariantJanus;
2178           break;
2179         case 49:
2180           v = VariantSuper;
2181           break;
2182         case 50:
2183           v = VariantGreat;
2184           break;
2185         case -1:
2186           /* Found "wild" or "w" in the string but no number;
2187              must assume it's normal chess. */
2188           v = VariantNormal;
2189           break;
2190         default:
2191           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2192           if( (len >= MSG_SIZ) && appData.debugMode )
2193             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2194
2195           DisplayError(buf, 0);
2196           v = VariantUnknown;
2197           break;
2198         }
2199       }
2200     }
2201     if (appData.debugMode) {
2202       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2203               e, wnum, VariantName(v));
2204     }
2205     return v;
2206 }
2207
2208 static int leftover_start = 0, leftover_len = 0;
2209 char star_match[STAR_MATCH_N][MSG_SIZ];
2210
2211 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2212    advance *index beyond it, and set leftover_start to the new value of
2213    *index; else return FALSE.  If pattern contains the character '*', it
2214    matches any sequence of characters not containing '\r', '\n', or the
2215    character following the '*' (if any), and the matched sequence(s) are
2216    copied into star_match.
2217    */
2218 int
2219 looking_at ( char *buf, int *index, char *pattern)
2220 {
2221     char *bufp = &buf[*index], *patternp = pattern;
2222     int star_count = 0;
2223     char *matchp = star_match[0];
2224
2225     for (;;) {
2226         if (*patternp == NULLCHAR) {
2227             *index = leftover_start = bufp - buf;
2228             *matchp = NULLCHAR;
2229             return TRUE;
2230         }
2231         if (*bufp == NULLCHAR) return FALSE;
2232         if (*patternp == '*') {
2233             if (*bufp == *(patternp + 1)) {
2234                 *matchp = NULLCHAR;
2235                 matchp = star_match[++star_count];
2236                 patternp += 2;
2237                 bufp++;
2238                 continue;
2239             } else if (*bufp == '\n' || *bufp == '\r') {
2240                 patternp++;
2241                 if (*patternp == NULLCHAR)
2242                   continue;
2243                 else
2244                   return FALSE;
2245             } else {
2246                 *matchp++ = *bufp++;
2247                 continue;
2248             }
2249         }
2250         if (*patternp != *bufp) return FALSE;
2251         patternp++;
2252         bufp++;
2253     }
2254 }
2255
2256 void
2257 SendToPlayer (char *data, int length)
2258 {
2259     int error, outCount;
2260     outCount = OutputToProcess(NoProc, data, length, &error);
2261     if (outCount < length) {
2262         DisplayFatalError(_("Error writing to display"), error, 1);
2263     }
2264 }
2265
2266 void
2267 PackHolding (char packed[], char *holding)
2268 {
2269     char *p = holding;
2270     char *q = packed;
2271     int runlength = 0;
2272     int curr = 9999;
2273     do {
2274         if (*p == curr) {
2275             runlength++;
2276         } else {
2277             switch (runlength) {
2278               case 0:
2279                 break;
2280               case 1:
2281                 *q++ = curr;
2282                 break;
2283               case 2:
2284                 *q++ = curr;
2285                 *q++ = curr;
2286                 break;
2287               default:
2288                 sprintf(q, "%d", runlength);
2289                 while (*q) q++;
2290                 *q++ = curr;
2291                 break;
2292             }
2293             runlength = 1;
2294             curr = *p;
2295         }
2296     } while (*p++);
2297     *q = NULLCHAR;
2298 }
2299
2300 /* Telnet protocol requests from the front end */
2301 void
2302 TelnetRequest (unsigned char ddww, unsigned char option)
2303 {
2304     unsigned char msg[3];
2305     int outCount, outError;
2306
2307     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2308
2309     if (appData.debugMode) {
2310         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2311         switch (ddww) {
2312           case TN_DO:
2313             ddwwStr = "DO";
2314             break;
2315           case TN_DONT:
2316             ddwwStr = "DONT";
2317             break;
2318           case TN_WILL:
2319             ddwwStr = "WILL";
2320             break;
2321           case TN_WONT:
2322             ddwwStr = "WONT";
2323             break;
2324           default:
2325             ddwwStr = buf1;
2326             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2327             break;
2328         }
2329         switch (option) {
2330           case TN_ECHO:
2331             optionStr = "ECHO";
2332             break;
2333           default:
2334             optionStr = buf2;
2335             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2336             break;
2337         }
2338         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2339     }
2340     msg[0] = TN_IAC;
2341     msg[1] = ddww;
2342     msg[2] = option;
2343     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2344     if (outCount < 3) {
2345         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2346     }
2347 }
2348
2349 void
2350 DoEcho ()
2351 {
2352     if (!appData.icsActive) return;
2353     TelnetRequest(TN_DO, TN_ECHO);
2354 }
2355
2356 void
2357 DontEcho ()
2358 {
2359     if (!appData.icsActive) return;
2360     TelnetRequest(TN_DONT, TN_ECHO);
2361 }
2362
2363 void
2364 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2365 {
2366     /* put the holdings sent to us by the server on the board holdings area */
2367     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2368     char p;
2369     ChessSquare piece;
2370
2371     if(gameInfo.holdingsWidth < 2)  return;
2372     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2373         return; // prevent overwriting by pre-board holdings
2374
2375     if( (int)lowestPiece >= BlackPawn ) {
2376         holdingsColumn = 0;
2377         countsColumn = 1;
2378         holdingsStartRow = BOARD_HEIGHT-1;
2379         direction = -1;
2380     } else {
2381         holdingsColumn = BOARD_WIDTH-1;
2382         countsColumn = BOARD_WIDTH-2;
2383         holdingsStartRow = 0;
2384         direction = 1;
2385     }
2386
2387     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2388         board[i][holdingsColumn] = EmptySquare;
2389         board[i][countsColumn]   = (ChessSquare) 0;
2390     }
2391     while( (p=*holdings++) != NULLCHAR ) {
2392         piece = CharToPiece( ToUpper(p) );
2393         if(piece == EmptySquare) continue;
2394         /*j = (int) piece - (int) WhitePawn;*/
2395         j = PieceToNumber(piece);
2396         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2397         if(j < 0) continue;               /* should not happen */
2398         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2399         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2400         board[holdingsStartRow+j*direction][countsColumn]++;
2401     }
2402 }
2403
2404
2405 void
2406 VariantSwitch (Board board, VariantClass newVariant)
2407 {
2408    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2409    static Board oldBoard;
2410
2411    startedFromPositionFile = FALSE;
2412    if(gameInfo.variant == newVariant) return;
2413
2414    /* [HGM] This routine is called each time an assignment is made to
2415     * gameInfo.variant during a game, to make sure the board sizes
2416     * are set to match the new variant. If that means adding or deleting
2417     * holdings, we shift the playing board accordingly
2418     * This kludge is needed because in ICS observe mode, we get boards
2419     * of an ongoing game without knowing the variant, and learn about the
2420     * latter only later. This can be because of the move list we requested,
2421     * in which case the game history is refilled from the beginning anyway,
2422     * but also when receiving holdings of a crazyhouse game. In the latter
2423     * case we want to add those holdings to the already received position.
2424     */
2425
2426
2427    if (appData.debugMode) {
2428      fprintf(debugFP, "Switch board from %s to %s\n",
2429              VariantName(gameInfo.variant), VariantName(newVariant));
2430      setbuf(debugFP, NULL);
2431    }
2432    shuffleOpenings = 0;       /* [HGM] shuffle */
2433    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2434    switch(newVariant)
2435      {
2436      case VariantShogi:
2437        newWidth = 9;  newHeight = 9;
2438        gameInfo.holdingsSize = 7;
2439      case VariantBughouse:
2440      case VariantCrazyhouse:
2441        newHoldingsWidth = 2; break;
2442      case VariantGreat:
2443        newWidth = 10;
2444      case VariantSuper:
2445        newHoldingsWidth = 2;
2446        gameInfo.holdingsSize = 8;
2447        break;
2448      case VariantGothic:
2449      case VariantCapablanca:
2450      case VariantCapaRandom:
2451        newWidth = 10;
2452      default:
2453        newHoldingsWidth = gameInfo.holdingsSize = 0;
2454      };
2455
2456    if(newWidth  != gameInfo.boardWidth  ||
2457       newHeight != gameInfo.boardHeight ||
2458       newHoldingsWidth != gameInfo.holdingsWidth ) {
2459
2460      /* shift position to new playing area, if needed */
2461      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2462        for(i=0; i<BOARD_HEIGHT; i++)
2463          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2464            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2465              board[i][j];
2466        for(i=0; i<newHeight; i++) {
2467          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2468          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2469        }
2470      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2471        for(i=0; i<BOARD_HEIGHT; i++)
2472          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2473            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2474              board[i][j];
2475      }
2476      board[HOLDINGS_SET] = 0;
2477      gameInfo.boardWidth  = newWidth;
2478      gameInfo.boardHeight = newHeight;
2479      gameInfo.holdingsWidth = newHoldingsWidth;
2480      gameInfo.variant = newVariant;
2481      InitDrawingSizes(-2, 0);
2482    } else gameInfo.variant = newVariant;
2483    CopyBoard(oldBoard, board);   // remember correctly formatted board
2484      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2485    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2486 }
2487
2488 static int loggedOn = FALSE;
2489
2490 /*-- Game start info cache: --*/
2491 int gs_gamenum;
2492 char gs_kind[MSG_SIZ];
2493 static char player1Name[128] = "";
2494 static char player2Name[128] = "";
2495 static char cont_seq[] = "\n\\   ";
2496 static int player1Rating = -1;
2497 static int player2Rating = -1;
2498 /*----------------------------*/
2499
2500 ColorClass curColor = ColorNormal;
2501 int suppressKibitz = 0;
2502
2503 // [HGM] seekgraph
2504 Boolean soughtPending = FALSE;
2505 Boolean seekGraphUp;
2506 #define MAX_SEEK_ADS 200
2507 #define SQUARE 0x80
2508 char *seekAdList[MAX_SEEK_ADS];
2509 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2510 float tcList[MAX_SEEK_ADS];
2511 char colorList[MAX_SEEK_ADS];
2512 int nrOfSeekAds = 0;
2513 int minRating = 1010, maxRating = 2800;
2514 int hMargin = 10, vMargin = 20, h, w;
2515 extern int squareSize, lineGap;
2516
2517 void
2518 PlotSeekAd (int i)
2519 {
2520         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2521         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2522         if(r < minRating+100 && r >=0 ) r = minRating+100;
2523         if(r > maxRating) r = maxRating;
2524         if(tc < 1.f) tc = 1.f;
2525         if(tc > 95.f) tc = 95.f;
2526         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2527         y = ((double)r - minRating)/(maxRating - minRating)
2528             * (h-vMargin-squareSize/8-1) + vMargin;
2529         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2530         if(strstr(seekAdList[i], " u ")) color = 1;
2531         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2532            !strstr(seekAdList[i], "bullet") &&
2533            !strstr(seekAdList[i], "blitz") &&
2534            !strstr(seekAdList[i], "standard") ) color = 2;
2535         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2536         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2537 }
2538
2539 void
2540 PlotSingleSeekAd (int i)
2541 {
2542         PlotSeekAd(i);
2543 }
2544
2545 void
2546 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2547 {
2548         char buf[MSG_SIZ], *ext = "";
2549         VariantClass v = StringToVariant(type);
2550         if(strstr(type, "wild")) {
2551             ext = type + 4; // append wild number
2552             if(v == VariantFischeRandom) type = "chess960"; else
2553             if(v == VariantLoadable) type = "setup"; else
2554             type = VariantName(v);
2555         }
2556         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2557         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2558             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2559             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2560             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2561             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2562             seekNrList[nrOfSeekAds] = nr;
2563             zList[nrOfSeekAds] = 0;
2564             seekAdList[nrOfSeekAds++] = StrSave(buf);
2565             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2566         }
2567 }
2568
2569 void
2570 EraseSeekDot (int i)
2571 {
2572     int x = xList[i], y = yList[i], d=squareSize/4, k;
2573     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2574     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2575     // now replot every dot that overlapped
2576     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2577         int xx = xList[k], yy = yList[k];
2578         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2579             DrawSeekDot(xx, yy, colorList[k]);
2580     }
2581 }
2582
2583 void
2584 RemoveSeekAd (int nr)
2585 {
2586         int i;
2587         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2588             EraseSeekDot(i);
2589             if(seekAdList[i]) free(seekAdList[i]);
2590             seekAdList[i] = seekAdList[--nrOfSeekAds];
2591             seekNrList[i] = seekNrList[nrOfSeekAds];
2592             ratingList[i] = ratingList[nrOfSeekAds];
2593             colorList[i]  = colorList[nrOfSeekAds];
2594             tcList[i] = tcList[nrOfSeekAds];
2595             xList[i]  = xList[nrOfSeekAds];
2596             yList[i]  = yList[nrOfSeekAds];
2597             zList[i]  = zList[nrOfSeekAds];
2598             seekAdList[nrOfSeekAds] = NULL;
2599             break;
2600         }
2601 }
2602
2603 Boolean
2604 MatchSoughtLine (char *line)
2605 {
2606     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2607     int nr, base, inc, u=0; char dummy;
2608
2609     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2610        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2611        (u=1) &&
2612        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2613         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2614         // match: compact and save the line
2615         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2616         return TRUE;
2617     }
2618     return FALSE;
2619 }
2620
2621 int
2622 DrawSeekGraph ()
2623 {
2624     int i;
2625     if(!seekGraphUp) return FALSE;
2626     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2627     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2628
2629     DrawSeekBackground(0, 0, w, h);
2630     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2631     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2632     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2633         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2634         yy = h-1-yy;
2635         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2636         if(i%500 == 0) {
2637             char buf[MSG_SIZ];
2638             snprintf(buf, MSG_SIZ, "%d", i);
2639             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2640         }
2641     }
2642     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2643     for(i=1; i<100; i+=(i<10?1:5)) {
2644         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2645         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2646         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2647             char buf[MSG_SIZ];
2648             snprintf(buf, MSG_SIZ, "%d", i);
2649             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2650         }
2651     }
2652     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2653     return TRUE;
2654 }
2655
2656 int
2657 SeekGraphClick (ClickType click, int x, int y, int moving)
2658 {
2659     static int lastDown = 0, displayed = 0, lastSecond;
2660     if(y < 0) return FALSE;
2661     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2662         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2663         if(!seekGraphUp) return FALSE;
2664         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2665         DrawPosition(TRUE, NULL);
2666         return TRUE;
2667     }
2668     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2669         if(click == Release || moving) return FALSE;
2670         nrOfSeekAds = 0;
2671         soughtPending = TRUE;
2672         SendToICS(ics_prefix);
2673         SendToICS("sought\n"); // should this be "sought all"?
2674     } else { // issue challenge based on clicked ad
2675         int dist = 10000; int i, closest = 0, second = 0;
2676         for(i=0; i<nrOfSeekAds; i++) {
2677             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2678             if(d < dist) { dist = d; closest = i; }
2679             second += (d - zList[i] < 120); // count in-range ads
2680             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2681         }
2682         if(dist < 120) {
2683             char buf[MSG_SIZ];
2684             second = (second > 1);
2685             if(displayed != closest || second != lastSecond) {
2686                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2687                 lastSecond = second; displayed = closest;
2688             }
2689             if(click == Press) {
2690                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2691                 lastDown = closest;
2692                 return TRUE;
2693             } // on press 'hit', only show info
2694             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2695             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2696             SendToICS(ics_prefix);
2697             SendToICS(buf);
2698             return TRUE; // let incoming board of started game pop down the graph
2699         } else if(click == Release) { // release 'miss' is ignored
2700             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2701             if(moving == 2) { // right up-click
2702                 nrOfSeekAds = 0; // refresh graph
2703                 soughtPending = TRUE;
2704                 SendToICS(ics_prefix);
2705                 SendToICS("sought\n"); // should this be "sought all"?
2706             }
2707             return TRUE;
2708         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2709         // press miss or release hit 'pop down' seek graph
2710         seekGraphUp = FALSE;
2711         DrawPosition(TRUE, NULL);
2712     }
2713     return TRUE;
2714 }
2715
2716 void
2717 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2718 {
2719 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2720 #define STARTED_NONE 0
2721 #define STARTED_MOVES 1
2722 #define STARTED_BOARD 2
2723 #define STARTED_OBSERVE 3
2724 #define STARTED_HOLDINGS 4
2725 #define STARTED_CHATTER 5
2726 #define STARTED_COMMENT 6
2727 #define STARTED_MOVES_NOHIDE 7
2728
2729     static int started = STARTED_NONE;
2730     static char parse[20000];
2731     static int parse_pos = 0;
2732     static char buf[BUF_SIZE + 1];
2733     static int firstTime = TRUE, intfSet = FALSE;
2734     static ColorClass prevColor = ColorNormal;
2735     static int savingComment = FALSE;
2736     static int cmatch = 0; // continuation sequence match
2737     char *bp;
2738     char str[MSG_SIZ];
2739     int i, oldi;
2740     int buf_len;
2741     int next_out;
2742     int tkind;
2743     int backup;    /* [DM] For zippy color lines */
2744     char *p;
2745     char talker[MSG_SIZ]; // [HGM] chat
2746     int channel;
2747
2748     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2749
2750     if (appData.debugMode) {
2751       if (!error) {
2752         fprintf(debugFP, "<ICS: ");
2753         show_bytes(debugFP, data, count);
2754         fprintf(debugFP, "\n");
2755       }
2756     }
2757
2758     if (appData.debugMode) { int f = forwardMostMove;
2759         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2760                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2761                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2762     }
2763     if (count > 0) {
2764         /* If last read ended with a partial line that we couldn't parse,
2765            prepend it to the new read and try again. */
2766         if (leftover_len > 0) {
2767             for (i=0; i<leftover_len; i++)
2768               buf[i] = buf[leftover_start + i];
2769         }
2770
2771     /* copy new characters into the buffer */
2772     bp = buf + leftover_len;
2773     buf_len=leftover_len;
2774     for (i=0; i<count; i++)
2775     {
2776         // ignore these
2777         if (data[i] == '\r')
2778             continue;
2779
2780         // join lines split by ICS?
2781         if (!appData.noJoin)
2782         {
2783             /*
2784                 Joining just consists of finding matches against the
2785                 continuation sequence, and discarding that sequence
2786                 if found instead of copying it.  So, until a match
2787                 fails, there's nothing to do since it might be the
2788                 complete sequence, and thus, something we don't want
2789                 copied.
2790             */
2791             if (data[i] == cont_seq[cmatch])
2792             {
2793                 cmatch++;
2794                 if (cmatch == strlen(cont_seq))
2795                 {
2796                     cmatch = 0; // complete match.  just reset the counter
2797
2798                     /*
2799                         it's possible for the ICS to not include the space
2800                         at the end of the last word, making our [correct]
2801                         join operation fuse two separate words.  the server
2802                         does this when the space occurs at the width setting.
2803                     */
2804                     if (!buf_len || buf[buf_len-1] != ' ')
2805                     {
2806                         *bp++ = ' ';
2807                         buf_len++;
2808                     }
2809                 }
2810                 continue;
2811             }
2812             else if (cmatch)
2813             {
2814                 /*
2815                     match failed, so we have to copy what matched before
2816                     falling through and copying this character.  In reality,
2817                     this will only ever be just the newline character, but
2818                     it doesn't hurt to be precise.
2819                 */
2820                 strncpy(bp, cont_seq, cmatch);
2821                 bp += cmatch;
2822                 buf_len += cmatch;
2823                 cmatch = 0;
2824             }
2825         }
2826
2827         // copy this char
2828         *bp++ = data[i];
2829         buf_len++;
2830     }
2831
2832         buf[buf_len] = NULLCHAR;
2833 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2834         next_out = 0;
2835         leftover_start = 0;
2836
2837         i = 0;
2838         while (i < buf_len) {
2839             /* Deal with part of the TELNET option negotiation
2840                protocol.  We refuse to do anything beyond the
2841                defaults, except that we allow the WILL ECHO option,
2842                which ICS uses to turn off password echoing when we are
2843                directly connected to it.  We reject this option
2844                if localLineEditing mode is on (always on in xboard)
2845                and we are talking to port 23, which might be a real
2846                telnet server that will try to keep WILL ECHO on permanently.
2847              */
2848             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2849                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2850                 unsigned char option;
2851                 oldi = i;
2852                 switch ((unsigned char) buf[++i]) {
2853                   case TN_WILL:
2854                     if (appData.debugMode)
2855                       fprintf(debugFP, "\n<WILL ");
2856                     switch (option = (unsigned char) buf[++i]) {
2857                       case TN_ECHO:
2858                         if (appData.debugMode)
2859                           fprintf(debugFP, "ECHO ");
2860                         /* Reply only if this is a change, according
2861                            to the protocol rules. */
2862                         if (remoteEchoOption) break;
2863                         if (appData.localLineEditing &&
2864                             atoi(appData.icsPort) == TN_PORT) {
2865                             TelnetRequest(TN_DONT, TN_ECHO);
2866                         } else {
2867                             EchoOff();
2868                             TelnetRequest(TN_DO, TN_ECHO);
2869                             remoteEchoOption = TRUE;
2870                         }
2871                         break;
2872                       default:
2873                         if (appData.debugMode)
2874                           fprintf(debugFP, "%d ", option);
2875                         /* Whatever this is, we don't want it. */
2876                         TelnetRequest(TN_DONT, option);
2877                         break;
2878                     }
2879                     break;
2880                   case TN_WONT:
2881                     if (appData.debugMode)
2882                       fprintf(debugFP, "\n<WONT ");
2883                     switch (option = (unsigned char) buf[++i]) {
2884                       case TN_ECHO:
2885                         if (appData.debugMode)
2886                           fprintf(debugFP, "ECHO ");
2887                         /* Reply only if this is a change, according
2888                            to the protocol rules. */
2889                         if (!remoteEchoOption) break;
2890                         EchoOn();
2891                         TelnetRequest(TN_DONT, TN_ECHO);
2892                         remoteEchoOption = FALSE;
2893                         break;
2894                       default:
2895                         if (appData.debugMode)
2896                           fprintf(debugFP, "%d ", (unsigned char) option);
2897                         /* Whatever this is, it must already be turned
2898                            off, because we never agree to turn on
2899                            anything non-default, so according to the
2900                            protocol rules, we don't reply. */
2901                         break;
2902                     }
2903                     break;
2904                   case TN_DO:
2905                     if (appData.debugMode)
2906                       fprintf(debugFP, "\n<DO ");
2907                     switch (option = (unsigned char) buf[++i]) {
2908                       default:
2909                         /* Whatever this is, we refuse to do it. */
2910                         if (appData.debugMode)
2911                           fprintf(debugFP, "%d ", option);
2912                         TelnetRequest(TN_WONT, option);
2913                         break;
2914                     }
2915                     break;
2916                   case TN_DONT:
2917                     if (appData.debugMode)
2918                       fprintf(debugFP, "\n<DONT ");
2919                     switch (option = (unsigned char) buf[++i]) {
2920                       default:
2921                         if (appData.debugMode)
2922                           fprintf(debugFP, "%d ", option);
2923                         /* Whatever this is, we are already not doing
2924                            it, because we never agree to do anything
2925                            non-default, so according to the protocol
2926                            rules, we don't reply. */
2927                         break;
2928                     }
2929                     break;
2930                   case TN_IAC:
2931                     if (appData.debugMode)
2932                       fprintf(debugFP, "\n<IAC ");
2933                     /* Doubled IAC; pass it through */
2934                     i--;
2935                     break;
2936                   default:
2937                     if (appData.debugMode)
2938                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2939                     /* Drop all other telnet commands on the floor */
2940                     break;
2941                 }
2942                 if (oldi > next_out)
2943                   SendToPlayer(&buf[next_out], oldi - next_out);
2944                 if (++i > next_out)
2945                   next_out = i;
2946                 continue;
2947             }
2948
2949             /* OK, this at least will *usually* work */
2950             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2951                 loggedOn = TRUE;
2952             }
2953
2954             if (loggedOn && !intfSet) {
2955                 if (ics_type == ICS_ICC) {
2956                   snprintf(str, MSG_SIZ,
2957                           "/set-quietly interface %s\n/set-quietly style 12\n",
2958                           programVersion);
2959                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2960                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2961                 } else if (ics_type == ICS_CHESSNET) {
2962                   snprintf(str, MSG_SIZ, "/style 12\n");
2963                 } else {
2964                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2965                   strcat(str, programVersion);
2966                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2967                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2968                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2969 #ifdef WIN32
2970                   strcat(str, "$iset nohighlight 1\n");
2971 #endif
2972                   strcat(str, "$iset lock 1\n$style 12\n");
2973                 }
2974                 SendToICS(str);
2975                 NotifyFrontendLogin();
2976                 intfSet = TRUE;
2977             }
2978
2979             if (started == STARTED_COMMENT) {
2980                 /* Accumulate characters in comment */
2981                 parse[parse_pos++] = buf[i];
2982                 if (buf[i] == '\n') {
2983                     parse[parse_pos] = NULLCHAR;
2984                     if(chattingPartner>=0) {
2985                         char mess[MSG_SIZ];
2986                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2987                         OutputChatMessage(chattingPartner, mess);
2988                         chattingPartner = -1;
2989                         next_out = i+1; // [HGM] suppress printing in ICS window
2990                     } else
2991                     if(!suppressKibitz) // [HGM] kibitz
2992                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2993                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2994                         int nrDigit = 0, nrAlph = 0, j;
2995                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2996                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2997                         parse[parse_pos] = NULLCHAR;
2998                         // try to be smart: if it does not look like search info, it should go to
2999                         // ICS interaction window after all, not to engine-output window.
3000                         for(j=0; j<parse_pos; j++) { // count letters and digits
3001                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3002                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3003                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3004                         }
3005                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3006                             int depth=0; float score;
3007                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3008                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3009                                 pvInfoList[forwardMostMove-1].depth = depth;
3010                                 pvInfoList[forwardMostMove-1].score = 100*score;
3011                             }
3012                             OutputKibitz(suppressKibitz, parse);
3013                         } else {
3014                             char tmp[MSG_SIZ];
3015                             if(gameMode == IcsObserving) // restore original ICS messages
3016                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3017                             else
3018                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3019                             SendToPlayer(tmp, strlen(tmp));
3020                         }
3021                         next_out = i+1; // [HGM] suppress printing in ICS window
3022                     }
3023                     started = STARTED_NONE;
3024                 } else {
3025                     /* Don't match patterns against characters in comment */
3026                     i++;
3027                     continue;
3028                 }
3029             }
3030             if (started == STARTED_CHATTER) {
3031                 if (buf[i] != '\n') {
3032                     /* Don't match patterns against characters in chatter */
3033                     i++;
3034                     continue;
3035                 }
3036                 started = STARTED_NONE;
3037                 if(suppressKibitz) next_out = i+1;
3038             }
3039
3040             /* Kludge to deal with rcmd protocol */
3041             if (firstTime && looking_at(buf, &i, "\001*")) {
3042                 DisplayFatalError(&buf[1], 0, 1);
3043                 continue;
3044             } else {
3045                 firstTime = FALSE;
3046             }
3047
3048             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3049                 ics_type = ICS_ICC;
3050                 ics_prefix = "/";
3051                 if (appData.debugMode)
3052                   fprintf(debugFP, "ics_type %d\n", ics_type);
3053                 continue;
3054             }
3055             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3056                 ics_type = ICS_FICS;
3057                 ics_prefix = "$";
3058                 if (appData.debugMode)
3059                   fprintf(debugFP, "ics_type %d\n", ics_type);
3060                 continue;
3061             }
3062             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3063                 ics_type = ICS_CHESSNET;
3064                 ics_prefix = "/";
3065                 if (appData.debugMode)
3066                   fprintf(debugFP, "ics_type %d\n", ics_type);
3067                 continue;
3068             }
3069
3070             if (!loggedOn &&
3071                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3072                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3073                  looking_at(buf, &i, "will be \"*\""))) {
3074               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3075               continue;
3076             }
3077
3078             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3079               char buf[MSG_SIZ];
3080               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3081               DisplayIcsInteractionTitle(buf);
3082               have_set_title = TRUE;
3083             }
3084
3085             /* skip finger notes */
3086             if (started == STARTED_NONE &&
3087                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3088                  (buf[i] == '1' && buf[i+1] == '0')) &&
3089                 buf[i+2] == ':' && buf[i+3] == ' ') {
3090               started = STARTED_CHATTER;
3091               i += 3;
3092               continue;
3093             }
3094
3095             oldi = i;
3096             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3097             if(appData.seekGraph) {
3098                 if(soughtPending && MatchSoughtLine(buf+i)) {
3099                     i = strstr(buf+i, "rated") - buf;
3100                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101                     next_out = leftover_start = i;
3102                     started = STARTED_CHATTER;
3103                     suppressKibitz = TRUE;
3104                     continue;
3105                 }
3106                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3107                         && looking_at(buf, &i, "* ads displayed")) {
3108                     soughtPending = FALSE;
3109                     seekGraphUp = TRUE;
3110                     DrawSeekGraph();
3111                     continue;
3112                 }
3113                 if(appData.autoRefresh) {
3114                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3115                         int s = (ics_type == ICS_ICC); // ICC format differs
3116                         if(seekGraphUp)
3117                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3118                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3119                         looking_at(buf, &i, "*% "); // eat prompt
3120                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3121                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3122                         next_out = i; // suppress
3123                         continue;
3124                     }
3125                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3126                         char *p = star_match[0];
3127                         while(*p) {
3128                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3129                             while(*p && *p++ != ' '); // next
3130                         }
3131                         looking_at(buf, &i, "*% "); // eat prompt
3132                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3133                         next_out = i;
3134                         continue;
3135                     }
3136                 }
3137             }
3138
3139             /* skip formula vars */
3140             if (started == STARTED_NONE &&
3141                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3142               started = STARTED_CHATTER;
3143               i += 3;
3144               continue;
3145             }
3146
3147             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3148             if (appData.autoKibitz && started == STARTED_NONE &&
3149                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3150                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3151                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3152                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3153                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3154                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3155                         suppressKibitz = TRUE;
3156                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3157                         next_out = i;
3158                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3159                                 && (gameMode == IcsPlayingWhite)) ||
3160                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3161                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3162                             started = STARTED_CHATTER; // own kibitz we simply discard
3163                         else {
3164                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3165                             parse_pos = 0; parse[0] = NULLCHAR;
3166                             savingComment = TRUE;
3167                             suppressKibitz = gameMode != IcsObserving ? 2 :
3168                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3169                         }
3170                         continue;
3171                 } else
3172                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3173                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3174                          && atoi(star_match[0])) {
3175                     // suppress the acknowledgements of our own autoKibitz
3176                     char *p;
3177                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3178                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3179                     SendToPlayer(star_match[0], strlen(star_match[0]));
3180                     if(looking_at(buf, &i, "*% ")) // eat prompt
3181                         suppressKibitz = FALSE;
3182                     next_out = i;
3183                     continue;
3184                 }
3185             } // [HGM] kibitz: end of patch
3186
3187             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3188
3189             // [HGM] chat: intercept tells by users for which we have an open chat window
3190             channel = -1;
3191             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3192                                            looking_at(buf, &i, "* whispers:") ||
3193                                            looking_at(buf, &i, "* kibitzes:") ||
3194                                            looking_at(buf, &i, "* shouts:") ||
3195                                            looking_at(buf, &i, "* c-shouts:") ||
3196                                            looking_at(buf, &i, "--> * ") ||
3197                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3198                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3199                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3200                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3201                 int p;
3202                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3203                 chattingPartner = -1;
3204
3205                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3206                 for(p=0; p<MAX_CHAT; p++) {
3207                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3208                     talker[0] = '['; strcat(talker, "] ");
3209                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3210                     chattingPartner = p; break;
3211                     }
3212                 } else
3213                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3214                 for(p=0; p<MAX_CHAT; p++) {
3215                     if(!strcmp("kibitzes", chatPartner[p])) {
3216                         talker[0] = '['; strcat(talker, "] ");
3217                         chattingPartner = p; break;
3218                     }
3219                 } else
3220                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3221                 for(p=0; p<MAX_CHAT; p++) {
3222                     if(!strcmp("whispers", chatPartner[p])) {
3223                         talker[0] = '['; strcat(talker, "] ");
3224                         chattingPartner = p; break;
3225                     }
3226                 } else
3227                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3228                   if(buf[i-8] == '-' && buf[i-3] == 't')
3229                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3230                     if(!strcmp("c-shouts", chatPartner[p])) {
3231                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3232                         chattingPartner = p; break;
3233                     }
3234                   }
3235                   if(chattingPartner < 0)
3236                   for(p=0; p<MAX_CHAT; p++) {
3237                     if(!strcmp("shouts", chatPartner[p])) {
3238                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3239                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3240                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3241                         chattingPartner = p; break;
3242                     }
3243                   }
3244                 }
3245                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3246                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3247                     talker[0] = 0; Colorize(ColorTell, FALSE);
3248                     chattingPartner = p; break;
3249                 }
3250                 if(chattingPartner<0) i = oldi; else {
3251                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3252                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3253                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3254                     started = STARTED_COMMENT;
3255                     parse_pos = 0; parse[0] = NULLCHAR;
3256                     savingComment = 3 + chattingPartner; // counts as TRUE
3257                     suppressKibitz = TRUE;
3258                     continue;
3259                 }
3260             } // [HGM] chat: end of patch
3261
3262           backup = i;
3263             if (appData.zippyTalk || appData.zippyPlay) {
3264                 /* [DM] Backup address for color zippy lines */
3265 #if ZIPPY
3266                if (loggedOn == TRUE)
3267                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3268                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3269 #endif
3270             } // [DM] 'else { ' deleted
3271                 if (
3272                     /* Regular tells and says */
3273                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3274                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3275                     looking_at(buf, &i, "* says: ") ||
3276                     /* Don't color "message" or "messages" output */
3277                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3278                     looking_at(buf, &i, "*. * at *:*: ") ||
3279                     looking_at(buf, &i, "--* (*:*): ") ||
3280                     /* Message notifications (same color as tells) */
3281                     looking_at(buf, &i, "* has left a message ") ||
3282                     looking_at(buf, &i, "* just sent you a message:\n") ||
3283                     /* Whispers and kibitzes */
3284                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3285                     looking_at(buf, &i, "* kibitzes: ") ||
3286                     /* Channel tells */
3287                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3288
3289                   if (tkind == 1 && strchr(star_match[0], ':')) {
3290                       /* Avoid "tells you:" spoofs in channels */
3291                      tkind = 3;
3292                   }
3293                   if (star_match[0][0] == NULLCHAR ||
3294                       strchr(star_match[0], ' ') ||
3295                       (tkind == 3 && strchr(star_match[1], ' '))) {
3296                     /* Reject bogus matches */
3297                     i = oldi;
3298                   } else {
3299                     if (appData.colorize) {
3300                       if (oldi > next_out) {
3301                         SendToPlayer(&buf[next_out], oldi - next_out);
3302                         next_out = oldi;
3303                       }
3304                       switch (tkind) {
3305                       case 1:
3306                         Colorize(ColorTell, FALSE);
3307                         curColor = ColorTell;
3308                         break;
3309                       case 2:
3310                         Colorize(ColorKibitz, FALSE);
3311                         curColor = ColorKibitz;
3312                         break;
3313                       case 3:
3314                         p = strrchr(star_match[1], '(');
3315                         if (p == NULL) {
3316                           p = star_match[1];
3317                         } else {
3318                           p++;
3319                         }
3320                         if (atoi(p) == 1) {
3321                           Colorize(ColorChannel1, FALSE);
3322                           curColor = ColorChannel1;
3323                         } else {
3324                           Colorize(ColorChannel, FALSE);
3325                           curColor = ColorChannel;
3326                         }
3327                         break;
3328                       case 5:
3329                         curColor = ColorNormal;
3330                         break;
3331                       }
3332                     }
3333                     if (started == STARTED_NONE && appData.autoComment &&
3334                         (gameMode == IcsObserving ||
3335                          gameMode == IcsPlayingWhite ||
3336                          gameMode == IcsPlayingBlack)) {
3337                       parse_pos = i - oldi;
3338                       memcpy(parse, &buf[oldi], parse_pos);
3339                       parse[parse_pos] = NULLCHAR;
3340                       started = STARTED_COMMENT;
3341                       savingComment = TRUE;
3342                     } else {
3343                       started = STARTED_CHATTER;
3344                       savingComment = FALSE;
3345                     }
3346                     loggedOn = TRUE;
3347                     continue;
3348                   }
3349                 }
3350
3351                 if (looking_at(buf, &i, "* s-shouts: ") ||
3352                     looking_at(buf, &i, "* c-shouts: ")) {
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorSShout, FALSE);
3359                         curColor = ColorSShout;
3360                     }
3361                     loggedOn = TRUE;
3362                     started = STARTED_CHATTER;
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "--->")) {
3367                     loggedOn = TRUE;
3368                     continue;
3369                 }
3370
3371                 if (looking_at(buf, &i, "* shouts: ") ||
3372                     looking_at(buf, &i, "--> ")) {
3373                     if (appData.colorize) {
3374                         if (oldi > next_out) {
3375                             SendToPlayer(&buf[next_out], oldi - next_out);
3376                             next_out = oldi;
3377                         }
3378                         Colorize(ColorShout, FALSE);
3379                         curColor = ColorShout;
3380                     }
3381                     loggedOn = TRUE;
3382                     started = STARTED_CHATTER;
3383                     continue;
3384                 }
3385
3386                 if (looking_at( buf, &i, "Challenge:")) {
3387                     if (appData.colorize) {
3388                         if (oldi > next_out) {
3389                             SendToPlayer(&buf[next_out], oldi - next_out);
3390                             next_out = oldi;
3391                         }
3392                         Colorize(ColorChallenge, FALSE);
3393                         curColor = ColorChallenge;
3394                     }
3395                     loggedOn = TRUE;
3396                     continue;
3397                 }
3398
3399                 if (looking_at(buf, &i, "* offers you") ||
3400                     looking_at(buf, &i, "* offers to be") ||
3401                     looking_at(buf, &i, "* would like to") ||
3402                     looking_at(buf, &i, "* requests to") ||
3403                     looking_at(buf, &i, "Your opponent offers") ||
3404                     looking_at(buf, &i, "Your opponent requests")) {
3405
3406                     if (appData.colorize) {
3407                         if (oldi > next_out) {
3408                             SendToPlayer(&buf[next_out], oldi - next_out);
3409                             next_out = oldi;
3410                         }
3411                         Colorize(ColorRequest, FALSE);
3412                         curColor = ColorRequest;
3413                     }
3414                     continue;
3415                 }
3416
3417                 if (looking_at(buf, &i, "* (*) seeking")) {
3418                     if (appData.colorize) {
3419                         if (oldi > next_out) {
3420                             SendToPlayer(&buf[next_out], oldi - next_out);
3421                             next_out = oldi;
3422                         }
3423                         Colorize(ColorSeek, FALSE);
3424                         curColor = ColorSeek;
3425                     }
3426                     continue;
3427             }
3428
3429           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3430
3431             if (looking_at(buf, &i, "\\   ")) {
3432                 if (prevColor != ColorNormal) {
3433                     if (oldi > next_out) {
3434                         SendToPlayer(&buf[next_out], oldi - next_out);
3435                         next_out = oldi;
3436                     }
3437                     Colorize(prevColor, TRUE);
3438                     curColor = prevColor;
3439                 }
3440                 if (savingComment) {
3441                     parse_pos = i - oldi;
3442                     memcpy(parse, &buf[oldi], parse_pos);
3443                     parse[parse_pos] = NULLCHAR;
3444                     started = STARTED_COMMENT;
3445                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3446                         chattingPartner = savingComment - 3; // kludge to remember the box
3447                 } else {
3448                     started = STARTED_CHATTER;
3449                 }
3450                 continue;
3451             }
3452
3453             if (looking_at(buf, &i, "Black Strength :") ||
3454                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3455                 looking_at(buf, &i, "<10>") ||
3456                 looking_at(buf, &i, "#@#")) {
3457                 /* Wrong board style */
3458                 loggedOn = TRUE;
3459                 SendToICS(ics_prefix);
3460                 SendToICS("set style 12\n");
3461                 SendToICS(ics_prefix);
3462                 SendToICS("refresh\n");
3463                 continue;
3464             }
3465
3466             if (looking_at(buf, &i, "login:")) {
3467               if (!have_sent_ICS_logon) {
3468                 if(ICSInitScript())
3469                   have_sent_ICS_logon = 1;
3470                 else // no init script was found
3471                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3472               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3473                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3474               }
3475                 continue;
3476             }
3477
3478             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3479                 (looking_at(buf, &i, "\n<12> ") ||
3480                  looking_at(buf, &i, "<12> "))) {
3481                 loggedOn = TRUE;
3482                 if (oldi > next_out) {
3483                     SendToPlayer(&buf[next_out], oldi - next_out);
3484                 }
3485                 next_out = i;
3486                 started = STARTED_BOARD;
3487                 parse_pos = 0;
3488                 continue;
3489             }
3490
3491             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3492                 looking_at(buf, &i, "<b1> ")) {
3493                 if (oldi > next_out) {
3494                     SendToPlayer(&buf[next_out], oldi - next_out);
3495                 }
3496                 next_out = i;
3497                 started = STARTED_HOLDINGS;
3498                 parse_pos = 0;
3499                 continue;
3500             }
3501
3502             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3503                 loggedOn = TRUE;
3504                 /* Header for a move list -- first line */
3505
3506                 switch (ics_getting_history) {
3507                   case H_FALSE:
3508                     switch (gameMode) {
3509                       case IcsIdle:
3510                       case BeginningOfGame:
3511                         /* User typed "moves" or "oldmoves" while we
3512                            were idle.  Pretend we asked for these
3513                            moves and soak them up so user can step
3514                            through them and/or save them.
3515                            */
3516                         Reset(FALSE, TRUE);
3517                         gameMode = IcsObserving;
3518                         ModeHighlight();
3519                         ics_gamenum = -1;
3520                         ics_getting_history = H_GOT_UNREQ_HEADER;
3521                         break;
3522                       case EditGame: /*?*/
3523                       case EditPosition: /*?*/
3524                         /* Should above feature work in these modes too? */
3525                         /* For now it doesn't */
3526                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3527                         break;
3528                       default:
3529                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3530                         break;
3531                     }
3532                     break;
3533                   case H_REQUESTED:
3534                     /* Is this the right one? */
3535                     if (gameInfo.white && gameInfo.black &&
3536                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3537                         strcmp(gameInfo.black, star_match[2]) == 0) {
3538                         /* All is well */
3539                         ics_getting_history = H_GOT_REQ_HEADER;
3540                     }
3541                     break;
3542                   case H_GOT_REQ_HEADER:
3543                   case H_GOT_UNREQ_HEADER:
3544                   case H_GOT_UNWANTED_HEADER:
3545                   case H_GETTING_MOVES:
3546                     /* Should not happen */
3547                     DisplayError(_("Error gathering move list: two headers"), 0);
3548                     ics_getting_history = H_FALSE;
3549                     break;
3550                 }
3551
3552                 /* Save player ratings into gameInfo if needed */
3553                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3554                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3555                     (gameInfo.whiteRating == -1 ||
3556                      gameInfo.blackRating == -1)) {
3557
3558                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3559                     gameInfo.blackRating = string_to_rating(star_match[3]);
3560                     if (appData.debugMode)
3561                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3562                               gameInfo.whiteRating, gameInfo.blackRating);
3563                 }
3564                 continue;
3565             }
3566
3567             if (looking_at(buf, &i,
3568               "* * match, initial time: * minute*, increment: * second")) {
3569                 /* Header for a move list -- second line */
3570                 /* Initial board will follow if this is a wild game */
3571                 if (gameInfo.event != NULL) free(gameInfo.event);
3572                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3573                 gameInfo.event = StrSave(str);
3574                 /* [HGM] we switched variant. Translate boards if needed. */
3575                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3576                 continue;
3577             }
3578
3579             if (looking_at(buf, &i, "Move  ")) {
3580                 /* Beginning of a move list */
3581                 switch (ics_getting_history) {
3582                   case H_FALSE:
3583                     /* Normally should not happen */
3584                     /* Maybe user hit reset while we were parsing */
3585                     break;
3586                   case H_REQUESTED:
3587                     /* Happens if we are ignoring a move list that is not
3588                      * the one we just requested.  Common if the user
3589                      * tries to observe two games without turning off
3590                      * getMoveList */
3591                     break;
3592                   case H_GETTING_MOVES:
3593                     /* Should not happen */
3594                     DisplayError(_("Error gathering move list: nested"), 0);
3595                     ics_getting_history = H_FALSE;
3596                     break;
3597                   case H_GOT_REQ_HEADER:
3598                     ics_getting_history = H_GETTING_MOVES;
3599                     started = STARTED_MOVES;
3600                     parse_pos = 0;
3601                     if (oldi > next_out) {
3602                         SendToPlayer(&buf[next_out], oldi - next_out);
3603                     }
3604                     break;
3605                   case H_GOT_UNREQ_HEADER:
3606                     ics_getting_history = H_GETTING_MOVES;
3607                     started = STARTED_MOVES_NOHIDE;
3608                     parse_pos = 0;
3609                     break;
3610                   case H_GOT_UNWANTED_HEADER:
3611                     ics_getting_history = H_FALSE;
3612                     break;
3613                 }
3614                 continue;
3615             }
3616
3617             if (looking_at(buf, &i, "% ") ||
3618                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3619                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3620                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3621                     soughtPending = FALSE;
3622                     seekGraphUp = TRUE;
3623                     DrawSeekGraph();
3624                 }
3625                 if(suppressKibitz) next_out = i;
3626                 savingComment = FALSE;
3627                 suppressKibitz = 0;
3628                 switch (started) {
3629                   case STARTED_MOVES:
3630                   case STARTED_MOVES_NOHIDE:
3631                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3632                     parse[parse_pos + i - oldi] = NULLCHAR;
3633                     ParseGameHistory(parse);
3634 #if ZIPPY
3635                     if (appData.zippyPlay && first.initDone) {
3636                         FeedMovesToProgram(&first, forwardMostMove);
3637                         if (gameMode == IcsPlayingWhite) {
3638                             if (WhiteOnMove(forwardMostMove)) {
3639                                 if (first.sendTime) {
3640                                   if (first.useColors) {
3641                                     SendToProgram("black\n", &first);
3642                                   }
3643                                   SendTimeRemaining(&first, TRUE);
3644                                 }
3645                                 if (first.useColors) {
3646                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3647                                 }
3648                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3649                                 first.maybeThinking = TRUE;
3650                             } else {
3651                                 if (first.usePlayother) {
3652                                   if (first.sendTime) {
3653                                     SendTimeRemaining(&first, TRUE);
3654                                   }
3655                                   SendToProgram("playother\n", &first);
3656                                   firstMove = FALSE;
3657                                 } else {
3658                                   firstMove = TRUE;
3659                                 }
3660                             }
3661                         } else if (gameMode == IcsPlayingBlack) {
3662                             if (!WhiteOnMove(forwardMostMove)) {
3663                                 if (first.sendTime) {
3664                                   if (first.useColors) {
3665                                     SendToProgram("white\n", &first);
3666                                   }
3667                                   SendTimeRemaining(&first, FALSE);
3668                                 }
3669                                 if (first.useColors) {
3670                                   SendToProgram("black\n", &first);
3671                                 }
3672                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3673                                 first.maybeThinking = TRUE;
3674                             } else {
3675                                 if (first.usePlayother) {
3676                                   if (first.sendTime) {
3677                                     SendTimeRemaining(&first, FALSE);
3678                                   }
3679                                   SendToProgram("playother\n", &first);
3680                                   firstMove = FALSE;
3681                                 } else {
3682                                   firstMove = TRUE;
3683                                 }
3684                             }
3685                         }
3686                     }
3687 #endif
3688                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3689                         /* Moves came from oldmoves or moves command
3690                            while we weren't doing anything else.
3691                            */
3692                         currentMove = forwardMostMove;
3693                         ClearHighlights();/*!!could figure this out*/
3694                         flipView = appData.flipView;
3695                         DrawPosition(TRUE, boards[currentMove]);
3696                         DisplayBothClocks();
3697                         snprintf(str, MSG_SIZ, "%s %s %s",
3698                                 gameInfo.white, _("vs."),  gameInfo.black);
3699                         DisplayTitle(str);
3700                         gameMode = IcsIdle;
3701                     } else {
3702                         /* Moves were history of an active game */
3703                         if (gameInfo.resultDetails != NULL) {
3704                             free(gameInfo.resultDetails);
3705                             gameInfo.resultDetails = NULL;
3706                         }
3707                     }
3708                     HistorySet(parseList, backwardMostMove,
3709                                forwardMostMove, currentMove-1);
3710                     DisplayMove(currentMove - 1);
3711                     if (started == STARTED_MOVES) next_out = i;
3712                     started = STARTED_NONE;
3713                     ics_getting_history = H_FALSE;
3714                     break;
3715
3716                   case STARTED_OBSERVE:
3717                     started = STARTED_NONE;
3718                     SendToICS(ics_prefix);
3719                     SendToICS("refresh\n");
3720                     break;
3721
3722                   default:
3723                     break;
3724                 }
3725                 if(bookHit) { // [HGM] book: simulate book reply
3726                     static char bookMove[MSG_SIZ]; // a bit generous?
3727
3728                     programStats.nodes = programStats.depth = programStats.time =
3729                     programStats.score = programStats.got_only_move = 0;
3730                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3731
3732                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3733                     strcat(bookMove, bookHit);
3734                     HandleMachineMove(bookMove, &first);
3735                 }
3736                 continue;
3737             }
3738
3739             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3740                  started == STARTED_HOLDINGS ||
3741                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3742                 /* Accumulate characters in move list or board */
3743                 parse[parse_pos++] = buf[i];
3744             }
3745
3746             /* Start of game messages.  Mostly we detect start of game
3747                when the first board image arrives.  On some versions
3748                of the ICS, though, we need to do a "refresh" after starting
3749                to observe in order to get the current board right away. */
3750             if (looking_at(buf, &i, "Adding game * to observation list")) {
3751                 started = STARTED_OBSERVE;
3752                 continue;
3753             }
3754
3755             /* Handle auto-observe */
3756             if (appData.autoObserve &&
3757                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3758                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3759                 char *player;
3760                 /* Choose the player that was highlighted, if any. */
3761                 if (star_match[0][0] == '\033' ||
3762                     star_match[1][0] != '\033') {
3763                     player = star_match[0];
3764                 } else {
3765                     player = star_match[2];
3766                 }
3767                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3768                         ics_prefix, StripHighlightAndTitle(player));
3769                 SendToICS(str);
3770
3771                 /* Save ratings from notify string */
3772                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3773                 player1Rating = string_to_rating(star_match[1]);
3774                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3775                 player2Rating = string_to_rating(star_match[3]);
3776
3777                 if (appData.debugMode)
3778                   fprintf(debugFP,
3779                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3780                           player1Name, player1Rating,
3781                           player2Name, player2Rating);
3782
3783                 continue;
3784             }
3785
3786             /* Deal with automatic examine mode after a game,
3787                and with IcsObserving -> IcsExamining transition */
3788             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3789                 looking_at(buf, &i, "has made you an examiner of game *")) {
3790
3791                 int gamenum = atoi(star_match[0]);
3792                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3793                     gamenum == ics_gamenum) {
3794                     /* We were already playing or observing this game;
3795                        no need to refetch history */
3796                     gameMode = IcsExamining;
3797                     if (pausing) {
3798                         pauseExamForwardMostMove = forwardMostMove;
3799                     } else if (currentMove < forwardMostMove) {
3800                         ForwardInner(forwardMostMove);
3801                     }
3802                 } else {
3803                     /* I don't think this case really can happen */
3804                     SendToICS(ics_prefix);
3805                     SendToICS("refresh\n");
3806                 }
3807                 continue;
3808             }
3809
3810             /* Error messages */
3811 //          if (ics_user_moved) {
3812             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3813                 if (looking_at(buf, &i, "Illegal move") ||
3814                     looking_at(buf, &i, "Not a legal move") ||
3815                     looking_at(buf, &i, "Your king is in check") ||
3816                     looking_at(buf, &i, "It isn't your turn") ||
3817                     looking_at(buf, &i, "It is not your move")) {
3818                     /* Illegal move */
3819                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3820                         currentMove = forwardMostMove-1;
3821                         DisplayMove(currentMove - 1); /* before DMError */
3822                         DrawPosition(FALSE, boards[currentMove]);
3823                         SwitchClocks(forwardMostMove-1); // [HGM] race
3824                         DisplayBothClocks();
3825                     }
3826                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3827                     ics_user_moved = 0;
3828                     continue;
3829                 }
3830             }
3831
3832             if (looking_at(buf, &i, "still have time") ||
3833                 looking_at(buf, &i, "not out of time") ||
3834                 looking_at(buf, &i, "either player is out of time") ||
3835                 looking_at(buf, &i, "has timeseal; checking")) {
3836                 /* We must have called his flag a little too soon */
3837                 whiteFlag = blackFlag = FALSE;
3838                 continue;
3839             }
3840
3841             if (looking_at(buf, &i, "added * seconds to") ||
3842                 looking_at(buf, &i, "seconds were added to")) {
3843                 /* Update the clocks */
3844                 SendToICS(ics_prefix);
3845                 SendToICS("refresh\n");
3846                 continue;
3847             }
3848
3849             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3850                 ics_clock_paused = TRUE;
3851                 StopClocks();
3852                 continue;
3853             }
3854
3855             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3856                 ics_clock_paused = FALSE;
3857                 StartClocks();
3858                 continue;
3859             }
3860
3861             /* Grab player ratings from the Creating: message.
3862                Note we have to check for the special case when
3863                the ICS inserts things like [white] or [black]. */
3864             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3865                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3866                 /* star_matches:
3867                    0    player 1 name (not necessarily white)
3868                    1    player 1 rating
3869                    2    empty, white, or black (IGNORED)
3870                    3    player 2 name (not necessarily black)
3871                    4    player 2 rating
3872
3873                    The names/ratings are sorted out when the game
3874                    actually starts (below).
3875                 */
3876                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3877                 player1Rating = string_to_rating(star_match[1]);
3878                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3879                 player2Rating = string_to_rating(star_match[4]);
3880
3881                 if (appData.debugMode)
3882                   fprintf(debugFP,
3883                           "Ratings from 'Creating:' %s %d, %s %d\n",
3884                           player1Name, player1Rating,
3885                           player2Name, player2Rating);
3886
3887                 continue;
3888             }
3889
3890             /* Improved generic start/end-of-game messages */
3891             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3892                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3893                 /* If tkind == 0: */
3894                 /* star_match[0] is the game number */
3895                 /*           [1] is the white player's name */
3896                 /*           [2] is the black player's name */
3897                 /* For end-of-game: */
3898                 /*           [3] is the reason for the game end */
3899                 /*           [4] is a PGN end game-token, preceded by " " */
3900                 /* For start-of-game: */
3901                 /*           [3] begins with "Creating" or "Continuing" */
3902                 /*           [4] is " *" or empty (don't care). */
3903                 int gamenum = atoi(star_match[0]);
3904                 char *whitename, *blackname, *why, *endtoken;
3905                 ChessMove endtype = EndOfFile;
3906
3907                 if (tkind == 0) {
3908                   whitename = star_match[1];
3909                   blackname = star_match[2];
3910                   why = star_match[3];
3911                   endtoken = star_match[4];
3912                 } else {
3913                   whitename = star_match[1];
3914                   blackname = star_match[3];
3915                   why = star_match[5];
3916                   endtoken = star_match[6];
3917                 }
3918
3919                 /* Game start messages */
3920                 if (strncmp(why, "Creating ", 9) == 0 ||
3921                     strncmp(why, "Continuing ", 11) == 0) {
3922                     gs_gamenum = gamenum;
3923                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3924                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3925                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3926 #if ZIPPY
3927                     if (appData.zippyPlay) {
3928                         ZippyGameStart(whitename, blackname);
3929                     }
3930 #endif /*ZIPPY*/
3931                     partnerBoardValid = FALSE; // [HGM] bughouse
3932                     continue;
3933                 }
3934
3935                 /* Game end messages */
3936                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3937                     ics_gamenum != gamenum) {
3938                     continue;
3939                 }
3940                 while (endtoken[0] == ' ') endtoken++;
3941                 switch (endtoken[0]) {
3942                   case '*':
3943                   default:
3944                     endtype = GameUnfinished;
3945                     break;
3946                   case '0':
3947                     endtype = BlackWins;
3948                     break;
3949                   case '1':
3950                     if (endtoken[1] == '/')
3951                       endtype = GameIsDrawn;
3952                     else
3953                       endtype = WhiteWins;
3954                     break;
3955                 }
3956                 GameEnds(endtype, why, GE_ICS);
3957 #if ZIPPY
3958                 if (appData.zippyPlay && first.initDone) {
3959                     ZippyGameEnd(endtype, why);
3960                     if (first.pr == NoProc) {
3961                       /* Start the next process early so that we'll
3962                          be ready for the next challenge */
3963                       StartChessProgram(&first);
3964                     }
3965                     /* Send "new" early, in case this command takes
3966                        a long time to finish, so that we'll be ready
3967                        for the next challenge. */
3968                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3969                     Reset(TRUE, TRUE);
3970                 }
3971 #endif /*ZIPPY*/
3972                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3973                 continue;
3974             }
3975
3976             if (looking_at(buf, &i, "Removing game * from observation") ||
3977                 looking_at(buf, &i, "no longer observing game *") ||
3978                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3979                 if (gameMode == IcsObserving &&
3980                     atoi(star_match[0]) == ics_gamenum)
3981                   {
3982                       /* icsEngineAnalyze */
3983                       if (appData.icsEngineAnalyze) {
3984                             ExitAnalyzeMode();
3985                             ModeHighlight();
3986                       }
3987                       StopClocks();
3988                       gameMode = IcsIdle;
3989                       ics_gamenum = -1;
3990                       ics_user_moved = FALSE;
3991                   }
3992                 continue;
3993             }
3994
3995             if (looking_at(buf, &i, "no longer examining game *")) {
3996                 if (gameMode == IcsExamining &&
3997                     atoi(star_match[0]) == ics_gamenum)
3998                   {
3999                       gameMode = IcsIdle;
4000                       ics_gamenum = -1;
4001                       ics_user_moved = FALSE;
4002                   }
4003                 continue;
4004             }
4005
4006             /* Advance leftover_start past any newlines we find,
4007                so only partial lines can get reparsed */
4008             if (looking_at(buf, &i, "\n")) {
4009                 prevColor = curColor;
4010                 if (curColor != ColorNormal) {
4011                     if (oldi > next_out) {
4012                         SendToPlayer(&buf[next_out], oldi - next_out);
4013                         next_out = oldi;
4014                     }
4015                     Colorize(ColorNormal, FALSE);
4016                     curColor = ColorNormal;
4017                 }
4018                 if (started == STARTED_BOARD) {
4019                     started = STARTED_NONE;
4020                     parse[parse_pos] = NULLCHAR;
4021                     ParseBoard12(parse);
4022                     ics_user_moved = 0;
4023
4024                     /* Send premove here */
4025                     if (appData.premove) {
4026                       char str[MSG_SIZ];
4027                       if (currentMove == 0 &&
4028                           gameMode == IcsPlayingWhite &&
4029                           appData.premoveWhite) {
4030                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4031                         if (appData.debugMode)
4032                           fprintf(debugFP, "Sending premove:\n");
4033                         SendToICS(str);
4034                       } else if (currentMove == 1 &&
4035                                  gameMode == IcsPlayingBlack &&
4036                                  appData.premoveBlack) {
4037                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4038                         if (appData.debugMode)
4039                           fprintf(debugFP, "Sending premove:\n");
4040                         SendToICS(str);
4041                       } else if (gotPremove) {
4042                         gotPremove = 0;
4043                         ClearPremoveHighlights();
4044                         if (appData.debugMode)
4045                           fprintf(debugFP, "Sending premove:\n");
4046                           UserMoveEvent(premoveFromX, premoveFromY,
4047                                         premoveToX, premoveToY,
4048                                         premovePromoChar);
4049                       }
4050                     }
4051
4052                     /* Usually suppress following prompt */
4053                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4054                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4055                         if (looking_at(buf, &i, "*% ")) {
4056                             savingComment = FALSE;
4057                             suppressKibitz = 0;
4058                         }
4059                     }
4060                     next_out = i;
4061                 } else if (started == STARTED_HOLDINGS) {
4062                     int gamenum;
4063                     char new_piece[MSG_SIZ];
4064                     started = STARTED_NONE;
4065                     parse[parse_pos] = NULLCHAR;
4066                     if (appData.debugMode)
4067                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4068                                                         parse, currentMove);
4069                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4070                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4071                         if (gameInfo.variant == VariantNormal) {
4072                           /* [HGM] We seem to switch variant during a game!
4073                            * Presumably no holdings were displayed, so we have
4074                            * to move the position two files to the right to
4075                            * create room for them!
4076                            */
4077                           VariantClass newVariant;
4078                           switch(gameInfo.boardWidth) { // base guess on board width
4079                                 case 9:  newVariant = VariantShogi; break;
4080                                 case 10: newVariant = VariantGreat; break;
4081                                 default: newVariant = VariantCrazyhouse; break;
4082                           }
4083                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4084                           /* Get a move list just to see the header, which
4085                              will tell us whether this is really bug or zh */
4086                           if (ics_getting_history == H_FALSE) {
4087                             ics_getting_history = H_REQUESTED;
4088                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4089                             SendToICS(str);
4090                           }
4091                         }
4092                         new_piece[0] = NULLCHAR;
4093                         sscanf(parse, "game %d white [%s black [%s <- %s",
4094                                &gamenum, white_holding, black_holding,
4095                                new_piece);
4096                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4097                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4098                         /* [HGM] copy holdings to board holdings area */
4099                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4100                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4101                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4102 #if ZIPPY
4103                         if (appData.zippyPlay && first.initDone) {
4104                             ZippyHoldings(white_holding, black_holding,
4105                                           new_piece);
4106                         }
4107 #endif /*ZIPPY*/
4108                         if (tinyLayout || smallLayout) {
4109                             char wh[16], bh[16];
4110                             PackHolding(wh, white_holding);
4111                             PackHolding(bh, black_holding);
4112                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4113                                     gameInfo.white, gameInfo.black);
4114                         } else {
4115                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4116                                     gameInfo.white, white_holding, _("vs."),
4117                                     gameInfo.black, black_holding);
4118                         }
4119                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4120                         DrawPosition(FALSE, boards[currentMove]);
4121                         DisplayTitle(str);
4122                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4123                         sscanf(parse, "game %d white [%s black [%s <- %s",
4124                                &gamenum, white_holding, black_holding,
4125                                new_piece);
4126                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4127                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4128                         /* [HGM] copy holdings to partner-board holdings area */
4129                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4130                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4131                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4132                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4133                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4134                       }
4135                     }
4136                     /* Suppress following prompt */
4137                     if (looking_at(buf, &i, "*% ")) {
4138                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4139                         savingComment = FALSE;
4140                         suppressKibitz = 0;
4141                     }
4142                     next_out = i;
4143                 }
4144                 continue;
4145             }
4146
4147             i++;                /* skip unparsed character and loop back */
4148         }
4149
4150         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4151 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4152 //          SendToPlayer(&buf[next_out], i - next_out);
4153             started != STARTED_HOLDINGS && leftover_start > next_out) {
4154             SendToPlayer(&buf[next_out], leftover_start - next_out);
4155             next_out = i;
4156         }
4157
4158         leftover_len = buf_len - leftover_start;
4159         /* if buffer ends with something we couldn't parse,
4160            reparse it after appending the next read */
4161
4162     } else if (count == 0) {
4163         RemoveInputSource(isr);
4164         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4165     } else {
4166         DisplayFatalError(_("Error reading from ICS"), error, 1);
4167     }
4168 }
4169
4170
4171 /* Board style 12 looks like this:
4172
4173    <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
4174
4175  * The "<12> " is stripped before it gets to this routine.  The two
4176  * trailing 0's (flip state and clock ticking) are later addition, and
4177  * some chess servers may not have them, or may have only the first.
4178  * Additional trailing fields may be added in the future.
4179  */
4180
4181 #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"
4182
4183 #define RELATION_OBSERVING_PLAYED    0
4184 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4185 #define RELATION_PLAYING_MYMOVE      1
4186 #define RELATION_PLAYING_NOTMYMOVE  -1
4187 #define RELATION_EXAMINING           2
4188 #define RELATION_ISOLATED_BOARD     -3
4189 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4190
4191 void
4192 ParseBoard12 (char *string)
4193 {
4194 #if ZIPPY
4195     int i, takeback;
4196     char *bookHit = NULL; // [HGM] book
4197 #endif
4198     GameMode newGameMode;
4199     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4200     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4201     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4202     char to_play, board_chars[200];
4203     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4204     char black[32], white[32];
4205     Board board;
4206     int prevMove = currentMove;
4207     int ticking = 2;
4208     ChessMove moveType;
4209     int fromX, fromY, toX, toY;
4210     char promoChar;
4211     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4212     Boolean weird = FALSE, reqFlag = FALSE;
4213
4214     fromX = fromY = toX = toY = -1;
4215
4216     newGame = FALSE;
4217
4218     if (appData.debugMode)
4219       fprintf(debugFP, "Parsing board: %s\n", string);
4220
4221     move_str[0] = NULLCHAR;
4222     elapsed_time[0] = NULLCHAR;
4223     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4224         int  i = 0, j;
4225         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4226             if(string[i] == ' ') { ranks++; files = 0; }
4227             else files++;
4228             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4229             i++;
4230         }
4231         for(j = 0; j <i; j++) board_chars[j] = string[j];
4232         board_chars[i] = '\0';
4233         string += i + 1;
4234     }
4235     n = sscanf(string, PATTERN, &to_play, &double_push,
4236                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4237                &gamenum, white, black, &relation, &basetime, &increment,
4238                &white_stren, &black_stren, &white_time, &black_time,
4239                &moveNum, str, elapsed_time, move_str, &ics_flip,
4240                &ticking);
4241
4242     if (n < 21) {
4243         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4244         DisplayError(str, 0);
4245         return;
4246     }
4247
4248     /* Convert the move number to internal form */
4249     moveNum = (moveNum - 1) * 2;
4250     if (to_play == 'B') moveNum++;
4251     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4252       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4253                         0, 1);
4254       return;
4255     }
4256
4257     switch (relation) {
4258       case RELATION_OBSERVING_PLAYED:
4259       case RELATION_OBSERVING_STATIC:
4260         if (gamenum == -1) {
4261             /* Old ICC buglet */
4262             relation = RELATION_OBSERVING_STATIC;
4263         }
4264         newGameMode = IcsObserving;
4265         break;
4266       case RELATION_PLAYING_MYMOVE:
4267       case RELATION_PLAYING_NOTMYMOVE:
4268         newGameMode =
4269           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4270             IcsPlayingWhite : IcsPlayingBlack;
4271         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4272         break;
4273       case RELATION_EXAMINING:
4274         newGameMode = IcsExamining;
4275         break;
4276       case RELATION_ISOLATED_BOARD:
4277       default:
4278         /* Just display this board.  If user was doing something else,
4279            we will forget about it until the next board comes. */
4280         newGameMode = IcsIdle;
4281         break;
4282       case RELATION_STARTING_POSITION:
4283         newGameMode = gameMode;
4284         break;
4285     }
4286
4287     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4288         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4289          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4290       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4291       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4292       static int lastBgGame = -1;
4293       char *toSqr;
4294       for (k = 0; k < ranks; k++) {
4295         for (j = 0; j < files; j++)
4296           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4297         if(gameInfo.holdingsWidth > 1) {
4298              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4299              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4300         }
4301       }
4302       CopyBoard(partnerBoard, board);
4303       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4304         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4305         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4306       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4307       if(toSqr = strchr(str, '-')) {
4308         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4309         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4310       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4311       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4312       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4313       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4314       if(twoBoards) {
4315           DisplayWhiteClock(white_time*fac, to_play == 'W');
4316           DisplayBlackClock(black_time*fac, to_play != 'W');
4317           activePartner = to_play;
4318           if(gamenum != lastBgGame) {
4319               char buf[MSG_SIZ];
4320               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4321               DisplayTitle(buf);
4322           }
4323           lastBgGame = gamenum;
4324           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4325                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4326       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4327                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4328       if(!twoBoards) DisplayMessage(partnerStatus, "");
4329         partnerBoardValid = TRUE;
4330       return;
4331     }
4332
4333     if(appData.dualBoard && appData.bgObserve) {
4334         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4335             SendToICS(ics_prefix), SendToICS("pobserve\n");
4336         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4337             char buf[MSG_SIZ];
4338             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4339             SendToICS(buf);
4340         }
4341     }
4342
4343     /* Modify behavior for initial board display on move listing
4344        of wild games.
4345        */
4346     switch (ics_getting_history) {
4347       case H_FALSE:
4348       case H_REQUESTED:
4349         break;
4350       case H_GOT_REQ_HEADER:
4351       case H_GOT_UNREQ_HEADER:
4352         /* This is the initial position of the current game */
4353         gamenum = ics_gamenum;
4354         moveNum = 0;            /* old ICS bug workaround */
4355         if (to_play == 'B') {
4356           startedFromSetupPosition = TRUE;
4357           blackPlaysFirst = TRUE;
4358           moveNum = 1;
4359           if (forwardMostMove == 0) forwardMostMove = 1;
4360           if (backwardMostMove == 0) backwardMostMove = 1;
4361           if (currentMove == 0) currentMove = 1;
4362         }
4363         newGameMode = gameMode;
4364         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4365         break;
4366       case H_GOT_UNWANTED_HEADER:
4367         /* This is an initial board that we don't want */
4368         return;
4369       case H_GETTING_MOVES:
4370         /* Should not happen */
4371         DisplayError(_("Error gathering move list: extra board"), 0);
4372         ics_getting_history = H_FALSE;
4373         return;
4374     }
4375
4376    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4377                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4378                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4379      /* [HGM] We seem to have switched variant unexpectedly
4380       * Try to guess new variant from board size
4381       */
4382           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4383           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4384           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4385           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4386           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4387           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4388           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4389           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4390           /* Get a move list just to see the header, which
4391              will tell us whether this is really bug or zh */
4392           if (ics_getting_history == H_FALSE) {
4393             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4394             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4395             SendToICS(str);
4396           }
4397     }
4398
4399     /* Take action if this is the first board of a new game, or of a
4400        different game than is currently being displayed.  */
4401     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4402         relation == RELATION_ISOLATED_BOARD) {
4403
4404         /* Forget the old game and get the history (if any) of the new one */
4405         if (gameMode != BeginningOfGame) {
4406           Reset(TRUE, TRUE);
4407         }
4408         newGame = TRUE;
4409         if (appData.autoRaiseBoard) BoardToTop();
4410         prevMove = -3;
4411         if (gamenum == -1) {
4412             newGameMode = IcsIdle;
4413         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4414                    appData.getMoveList && !reqFlag) {
4415             /* Need to get game history */
4416             ics_getting_history = H_REQUESTED;
4417             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4418             SendToICS(str);
4419         }
4420
4421         /* Initially flip the board to have black on the bottom if playing
4422            black or if the ICS flip flag is set, but let the user change
4423            it with the Flip View button. */
4424         flipView = appData.autoFlipView ?
4425           (newGameMode == IcsPlayingBlack) || ics_flip :
4426           appData.flipView;
4427
4428         /* Done with values from previous mode; copy in new ones */
4429         gameMode = newGameMode;
4430         ModeHighlight();
4431         ics_gamenum = gamenum;
4432         if (gamenum == gs_gamenum) {
4433             int klen = strlen(gs_kind);
4434             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4435             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4436             gameInfo.event = StrSave(str);
4437         } else {
4438             gameInfo.event = StrSave("ICS game");
4439         }
4440         gameInfo.site = StrSave(appData.icsHost);
4441         gameInfo.date = PGNDate();
4442         gameInfo.round = StrSave("-");
4443         gameInfo.white = StrSave(white);
4444         gameInfo.black = StrSave(black);
4445         timeControl = basetime * 60 * 1000;
4446         timeControl_2 = 0;
4447         timeIncrement = increment * 1000;
4448         movesPerSession = 0;
4449         gameInfo.timeControl = TimeControlTagValue();
4450         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4451   if (appData.debugMode) {
4452     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4453     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4454     setbuf(debugFP, NULL);
4455   }
4456
4457         gameInfo.outOfBook = NULL;
4458
4459         /* Do we have the ratings? */
4460         if (strcmp(player1Name, white) == 0 &&
4461             strcmp(player2Name, black) == 0) {
4462             if (appData.debugMode)
4463               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4464                       player1Rating, player2Rating);
4465             gameInfo.whiteRating = player1Rating;
4466             gameInfo.blackRating = player2Rating;
4467         } else if (strcmp(player2Name, white) == 0 &&
4468                    strcmp(player1Name, black) == 0) {
4469             if (appData.debugMode)
4470               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4471                       player2Rating, player1Rating);
4472             gameInfo.whiteRating = player2Rating;
4473             gameInfo.blackRating = player1Rating;
4474         }
4475         player1Name[0] = player2Name[0] = NULLCHAR;
4476
4477         /* Silence shouts if requested */
4478         if (appData.quietPlay &&
4479             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4480             SendToICS(ics_prefix);
4481             SendToICS("set shout 0\n");
4482         }
4483     }
4484
4485     /* Deal with midgame name changes */
4486     if (!newGame) {
4487         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4488             if (gameInfo.white) free(gameInfo.white);
4489             gameInfo.white = StrSave(white);
4490         }
4491         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4492             if (gameInfo.black) free(gameInfo.black);
4493             gameInfo.black = StrSave(black);
4494         }
4495     }
4496
4497     /* Throw away game result if anything actually changes in examine mode */
4498     if (gameMode == IcsExamining && !newGame) {
4499         gameInfo.result = GameUnfinished;
4500         if (gameInfo.resultDetails != NULL) {
4501             free(gameInfo.resultDetails);
4502             gameInfo.resultDetails = NULL;
4503         }
4504     }
4505
4506     /* In pausing && IcsExamining mode, we ignore boards coming
4507        in if they are in a different variation than we are. */
4508     if (pauseExamInvalid) return;
4509     if (pausing && gameMode == IcsExamining) {
4510         if (moveNum <= pauseExamForwardMostMove) {
4511             pauseExamInvalid = TRUE;
4512             forwardMostMove = pauseExamForwardMostMove;
4513             return;
4514         }
4515     }
4516
4517   if (appData.debugMode) {
4518     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4519   }
4520     /* Parse the board */
4521     for (k = 0; k < ranks; k++) {
4522       for (j = 0; j < files; j++)
4523         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4524       if(gameInfo.holdingsWidth > 1) {
4525            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4526            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4527       }
4528     }
4529     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4530       board[5][BOARD_RGHT+1] = WhiteAngel;
4531       board[6][BOARD_RGHT+1] = WhiteMarshall;
4532       board[1][0] = BlackMarshall;
4533       board[2][0] = BlackAngel;
4534       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4535     }
4536     CopyBoard(boards[moveNum], board);
4537     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4538     if (moveNum == 0) {
4539         startedFromSetupPosition =
4540           !CompareBoards(board, initialPosition);
4541         if(startedFromSetupPosition)
4542             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4543     }
4544
4545     /* [HGM] Set castling rights. Take the outermost Rooks,
4546        to make it also work for FRC opening positions. Note that board12
4547        is really defective for later FRC positions, as it has no way to
4548        indicate which Rook can castle if they are on the same side of King.
4549        For the initial position we grant rights to the outermost Rooks,
4550        and remember thos rights, and we then copy them on positions
4551        later in an FRC game. This means WB might not recognize castlings with
4552        Rooks that have moved back to their original position as illegal,
4553        but in ICS mode that is not its job anyway.
4554     */
4555     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4556     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4557
4558         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4559             if(board[0][i] == WhiteRook) j = i;
4560         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4561         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4562             if(board[0][i] == WhiteRook) j = i;
4563         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4564         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4565             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4566         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4567         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4568             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4569         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4570
4571         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4572         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4573         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4574             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4575         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4576             if(board[BOARD_HEIGHT-1][k] == bKing)
4577                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4578         if(gameInfo.variant == VariantTwoKings) {
4579             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4580             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4581             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4582         }
4583     } else { int r;
4584         r = boards[moveNum][CASTLING][0] = initialRights[0];
4585         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4586         r = boards[moveNum][CASTLING][1] = initialRights[1];
4587         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4588         r = boards[moveNum][CASTLING][3] = initialRights[3];
4589         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4590         r = boards[moveNum][CASTLING][4] = initialRights[4];
4591         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4592         /* wildcastle kludge: always assume King has rights */
4593         r = boards[moveNum][CASTLING][2] = initialRights[2];
4594         r = boards[moveNum][CASTLING][5] = initialRights[5];
4595     }
4596     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4597     boards[moveNum][EP_STATUS] = EP_NONE;
4598     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4599     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4600     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4601
4602
4603     if (ics_getting_history == H_GOT_REQ_HEADER ||
4604         ics_getting_history == H_GOT_UNREQ_HEADER) {
4605         /* This was an initial position from a move list, not
4606            the current position */
4607         return;
4608     }
4609
4610     /* Update currentMove and known move number limits */
4611     newMove = newGame || moveNum > forwardMostMove;
4612
4613     if (newGame) {
4614         forwardMostMove = backwardMostMove = currentMove = moveNum;
4615         if (gameMode == IcsExamining && moveNum == 0) {
4616           /* Workaround for ICS limitation: we are not told the wild
4617              type when starting to examine a game.  But if we ask for
4618              the move list, the move list header will tell us */
4619             ics_getting_history = H_REQUESTED;
4620             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4621             SendToICS(str);
4622         }
4623     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4624                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4625 #if ZIPPY
4626         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4627         /* [HGM] applied this also to an engine that is silently watching        */
4628         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4629             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4630             gameInfo.variant == currentlyInitializedVariant) {
4631           takeback = forwardMostMove - moveNum;
4632           for (i = 0; i < takeback; i++) {
4633             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4634             SendToProgram("undo\n", &first);
4635           }
4636         }
4637 #endif
4638
4639         forwardMostMove = moveNum;
4640         if (!pausing || currentMove > forwardMostMove)
4641           currentMove = forwardMostMove;
4642     } else {
4643         /* New part of history that is not contiguous with old part */
4644         if (pausing && gameMode == IcsExamining) {
4645             pauseExamInvalid = TRUE;
4646             forwardMostMove = pauseExamForwardMostMove;
4647             return;
4648         }
4649         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4650 #if ZIPPY
4651             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4652                 // [HGM] when we will receive the move list we now request, it will be
4653                 // fed to the engine from the first move on. So if the engine is not
4654                 // in the initial position now, bring it there.
4655                 InitChessProgram(&first, 0);
4656             }
4657 #endif
4658             ics_getting_history = H_REQUESTED;
4659             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4660             SendToICS(str);
4661         }
4662         forwardMostMove = backwardMostMove = currentMove = moveNum;
4663     }
4664
4665     /* Update the clocks */
4666     if (strchr(elapsed_time, '.')) {
4667       /* Time is in ms */
4668       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4669       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4670     } else {
4671       /* Time is in seconds */
4672       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4673       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4674     }
4675
4676
4677 #if ZIPPY
4678     if (appData.zippyPlay && newGame &&
4679         gameMode != IcsObserving && gameMode != IcsIdle &&
4680         gameMode != IcsExamining)
4681       ZippyFirstBoard(moveNum, basetime, increment);
4682 #endif
4683
4684     /* Put the move on the move list, first converting
4685        to canonical algebraic form. */
4686     if (moveNum > 0) {
4687   if (appData.debugMode) {
4688     int f = forwardMostMove;
4689     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4690             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4691             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4692     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4693     fprintf(debugFP, "moveNum = %d\n", moveNum);
4694     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4695     setbuf(debugFP, NULL);
4696   }
4697         if (moveNum <= backwardMostMove) {
4698             /* We don't know what the board looked like before
4699                this move.  Punt. */
4700           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4701             strcat(parseList[moveNum - 1], " ");
4702             strcat(parseList[moveNum - 1], elapsed_time);
4703             moveList[moveNum - 1][0] = NULLCHAR;
4704         } else if (strcmp(move_str, "none") == 0) {
4705             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4706             /* Again, we don't know what the board looked like;
4707                this is really the start of the game. */
4708             parseList[moveNum - 1][0] = NULLCHAR;
4709             moveList[moveNum - 1][0] = NULLCHAR;
4710             backwardMostMove = moveNum;
4711             startedFromSetupPosition = TRUE;
4712             fromX = fromY = toX = toY = -1;
4713         } else {
4714           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4715           //                 So we parse the long-algebraic move string in stead of the SAN move
4716           int valid; char buf[MSG_SIZ], *prom;
4717
4718           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4719                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4720           // str looks something like "Q/a1-a2"; kill the slash
4721           if(str[1] == '/')
4722             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4723           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4724           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4725                 strcat(buf, prom); // long move lacks promo specification!
4726           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4727                 if(appData.debugMode)
4728                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4729                 safeStrCpy(move_str, buf, MSG_SIZ);
4730           }
4731           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4732                                 &fromX, &fromY, &toX, &toY, &promoChar)
4733                || ParseOneMove(buf, moveNum - 1, &moveType,
4734                                 &fromX, &fromY, &toX, &toY, &promoChar);
4735           // end of long SAN patch
4736           if (valid) {
4737             (void) CoordsToAlgebraic(boards[moveNum - 1],
4738                                      PosFlags(moveNum - 1),
4739                                      fromY, fromX, toY, toX, promoChar,
4740                                      parseList[moveNum-1]);
4741             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4742               case MT_NONE:
4743               case MT_STALEMATE:
4744               default:
4745                 break;
4746               case MT_CHECK:
4747                 if(gameInfo.variant != VariantShogi)
4748                     strcat(parseList[moveNum - 1], "+");
4749                 break;
4750               case MT_CHECKMATE:
4751               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4752                 strcat(parseList[moveNum - 1], "#");
4753                 break;
4754             }
4755             strcat(parseList[moveNum - 1], " ");
4756             strcat(parseList[moveNum - 1], elapsed_time);
4757             /* currentMoveString is set as a side-effect of ParseOneMove */
4758             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4759             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4760             strcat(moveList[moveNum - 1], "\n");
4761
4762             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4763                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4764               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4765                 ChessSquare old, new = boards[moveNum][k][j];
4766                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4767                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4768                   if(old == new) continue;
4769                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4770                   else if(new == WhiteWazir || new == BlackWazir) {
4771                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4772                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4773                       else boards[moveNum][k][j] = old; // preserve type of Gold
4774                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4775                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4776               }
4777           } else {
4778             /* Move from ICS was illegal!?  Punt. */
4779             if (appData.debugMode) {
4780               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4781               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4782             }
4783             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4784             strcat(parseList[moveNum - 1], " ");
4785             strcat(parseList[moveNum - 1], elapsed_time);
4786             moveList[moveNum - 1][0] = NULLCHAR;
4787             fromX = fromY = toX = toY = -1;
4788           }
4789         }
4790   if (appData.debugMode) {
4791     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4792     setbuf(debugFP, NULL);
4793   }
4794
4795 #if ZIPPY
4796         /* Send move to chess program (BEFORE animating it). */
4797         if (appData.zippyPlay && !newGame && newMove &&
4798            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4799
4800             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4801                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4802                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4803                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4804                             move_str);
4805                     DisplayError(str, 0);
4806                 } else {
4807                     if (first.sendTime) {
4808                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4809                     }
4810                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4811                     if (firstMove && !bookHit) {
4812                         firstMove = FALSE;
4813                         if (first.useColors) {
4814                           SendToProgram(gameMode == IcsPlayingWhite ?
4815                                         "white\ngo\n" :
4816                                         "black\ngo\n", &first);
4817                         } else {
4818                           SendToProgram("go\n", &first);
4819                         }
4820                         first.maybeThinking = TRUE;
4821                     }
4822                 }
4823             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4824               if (moveList[moveNum - 1][0] == NULLCHAR) {
4825                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4826                 DisplayError(str, 0);
4827               } else {
4828                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4829                 SendMoveToProgram(moveNum - 1, &first);
4830               }
4831             }
4832         }
4833 #endif
4834     }
4835
4836     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4837         /* If move comes from a remote source, animate it.  If it
4838            isn't remote, it will have already been animated. */
4839         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4840             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4841         }
4842         if (!pausing && appData.highlightLastMove) {
4843             SetHighlights(fromX, fromY, toX, toY);
4844         }
4845     }
4846
4847     /* Start the clocks */
4848     whiteFlag = blackFlag = FALSE;
4849     appData.clockMode = !(basetime == 0 && increment == 0);
4850     if (ticking == 0) {
4851       ics_clock_paused = TRUE;
4852       StopClocks();
4853     } else if (ticking == 1) {
4854       ics_clock_paused = FALSE;
4855     }
4856     if (gameMode == IcsIdle ||
4857         relation == RELATION_OBSERVING_STATIC ||
4858         relation == RELATION_EXAMINING ||
4859         ics_clock_paused)
4860       DisplayBothClocks();
4861     else
4862       StartClocks();
4863
4864     /* Display opponents and material strengths */
4865     if (gameInfo.variant != VariantBughouse &&
4866         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4867         if (tinyLayout || smallLayout) {
4868             if(gameInfo.variant == VariantNormal)
4869               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4871                     basetime, increment);
4872             else
4873               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4875                     basetime, increment, (int) gameInfo.variant);
4876         } else {
4877             if(gameInfo.variant == VariantNormal)
4878               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4879                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4880                     basetime, increment);
4881             else
4882               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4883                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4884                     basetime, increment, VariantName(gameInfo.variant));
4885         }
4886         DisplayTitle(str);
4887   if (appData.debugMode) {
4888     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4889   }
4890     }
4891
4892
4893     /* Display the board */
4894     if (!pausing && !appData.noGUI) {
4895
4896       if (appData.premove)
4897           if (!gotPremove ||
4898              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4899              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4900               ClearPremoveHighlights();
4901
4902       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4903         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4904       DrawPosition(j, boards[currentMove]);
4905
4906       DisplayMove(moveNum - 1);
4907       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4908             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4909               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4910         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4911       }
4912     }
4913
4914     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4915 #if ZIPPY
4916     if(bookHit) { // [HGM] book: simulate book reply
4917         static char bookMove[MSG_SIZ]; // a bit generous?
4918
4919         programStats.nodes = programStats.depth = programStats.time =
4920         programStats.score = programStats.got_only_move = 0;
4921         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4922
4923         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4924         strcat(bookMove, bookHit);
4925         HandleMachineMove(bookMove, &first);
4926     }
4927 #endif
4928 }
4929
4930 void
4931 GetMoveListEvent ()
4932 {
4933     char buf[MSG_SIZ];
4934     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4935         ics_getting_history = H_REQUESTED;
4936         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4937         SendToICS(buf);
4938     }
4939 }
4940
4941 void
4942 SendToBoth (char *msg)
4943 {   // to make it easy to keep two engines in step in dual analysis
4944     SendToProgram(msg, &first);
4945     if(second.analyzing) SendToProgram(msg, &second);
4946 }
4947
4948 void
4949 AnalysisPeriodicEvent (int force)
4950 {
4951     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4952          && !force) || !appData.periodicUpdates)
4953       return;
4954
4955     /* Send . command to Crafty to collect stats */
4956     SendToBoth(".\n");
4957
4958     /* Don't send another until we get a response (this makes
4959        us stop sending to old Crafty's which don't understand
4960        the "." command (sending illegal cmds resets node count & time,
4961        which looks bad)) */
4962     programStats.ok_to_send = 0;
4963 }
4964
4965 void
4966 ics_update_width (int new_width)
4967 {
4968         ics_printf("set width %d\n", new_width);
4969 }
4970
4971 void
4972 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4973 {
4974     char buf[MSG_SIZ];
4975
4976     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4977         // null move in variant where engine does not understand it (for analysis purposes)
4978         SendBoard(cps, moveNum + 1); // send position after move in stead.
4979         return;
4980     }
4981     if (cps->useUsermove) {
4982       SendToProgram("usermove ", cps);
4983     }
4984     if (cps->useSAN) {
4985       char *space;
4986       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4987         int len = space - parseList[moveNum];
4988         memcpy(buf, parseList[moveNum], len);
4989         buf[len++] = '\n';
4990         buf[len] = NULLCHAR;
4991       } else {
4992         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4993       }
4994       SendToProgram(buf, cps);
4995     } else {
4996       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4997         AlphaRank(moveList[moveNum], 4);
4998         SendToProgram(moveList[moveNum], cps);
4999         AlphaRank(moveList[moveNum], 4); // and back
5000       } else
5001       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5002        * the engine. It would be nice to have a better way to identify castle
5003        * moves here. */
5004       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5005                                                                          && cps->useOOCastle) {
5006         int fromX = moveList[moveNum][0] - AAA;
5007         int fromY = moveList[moveNum][1] - ONE;
5008         int toX = moveList[moveNum][2] - AAA;
5009         int toY = moveList[moveNum][3] - ONE;
5010         if((boards[moveNum][fromY][fromX] == WhiteKing
5011             && boards[moveNum][toY][toX] == WhiteRook)
5012            || (boards[moveNum][fromY][fromX] == BlackKing
5013                && boards[moveNum][toY][toX] == BlackRook)) {
5014           if(toX > fromX) SendToProgram("O-O\n", cps);
5015           else SendToProgram("O-O-O\n", cps);
5016         }
5017         else SendToProgram(moveList[moveNum], cps);
5018       } else
5019       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5020         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5021           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5022           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5023                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5024         } else
5025           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5026                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5027         SendToProgram(buf, cps);
5028       }
5029       else SendToProgram(moveList[moveNum], cps);
5030       /* End of additions by Tord */
5031     }
5032
5033     /* [HGM] setting up the opening has brought engine in force mode! */
5034     /*       Send 'go' if we are in a mode where machine should play. */
5035     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5036         (gameMode == TwoMachinesPlay   ||
5037 #if ZIPPY
5038          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5039 #endif
5040          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5041         SendToProgram("go\n", cps);
5042   if (appData.debugMode) {
5043     fprintf(debugFP, "(extra)\n");
5044   }
5045     }
5046     setboardSpoiledMachineBlack = 0;
5047 }
5048
5049 void
5050 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5051 {
5052     char user_move[MSG_SIZ];
5053     char suffix[4];
5054
5055     if(gameInfo.variant == VariantSChess && promoChar) {
5056         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5057         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5058     } else suffix[0] = NULLCHAR;
5059
5060     switch (moveType) {
5061       default:
5062         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5063                 (int)moveType, fromX, fromY, toX, toY);
5064         DisplayError(user_move + strlen("say "), 0);
5065         break;
5066       case WhiteKingSideCastle:
5067       case BlackKingSideCastle:
5068       case WhiteQueenSideCastleWild:
5069       case BlackQueenSideCastleWild:
5070       /* PUSH Fabien */
5071       case WhiteHSideCastleFR:
5072       case BlackHSideCastleFR:
5073       /* POP Fabien */
5074         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5075         break;
5076       case WhiteQueenSideCastle:
5077       case BlackQueenSideCastle:
5078       case WhiteKingSideCastleWild:
5079       case BlackKingSideCastleWild:
5080       /* PUSH Fabien */
5081       case WhiteASideCastleFR:
5082       case BlackASideCastleFR:
5083       /* POP Fabien */
5084         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5085         break;
5086       case WhiteNonPromotion:
5087       case BlackNonPromotion:
5088         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5089         break;
5090       case WhitePromotion:
5091       case BlackPromotion:
5092         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5093           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5094                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5095                 PieceToChar(WhiteFerz));
5096         else if(gameInfo.variant == VariantGreat)
5097           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5098                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5099                 PieceToChar(WhiteMan));
5100         else
5101           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5102                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5103                 promoChar);
5104         break;
5105       case WhiteDrop:
5106       case BlackDrop:
5107       drop:
5108         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5109                  ToUpper(PieceToChar((ChessSquare) fromX)),
5110                  AAA + toX, ONE + toY);
5111         break;
5112       case IllegalMove:  /* could be a variant we don't quite understand */
5113         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5114       case NormalMove:
5115       case WhiteCapturesEnPassant:
5116       case BlackCapturesEnPassant:
5117         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5118                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5119         break;
5120     }
5121     SendToICS(user_move);
5122     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5123         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5124 }
5125
5126 void
5127 UploadGameEvent ()
5128 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5129     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5130     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5131     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5132       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5133       return;
5134     }
5135     if(gameMode != IcsExamining) { // is this ever not the case?
5136         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5137
5138         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5139           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5140         } else { // on FICS we must first go to general examine mode
5141           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5142         }
5143         if(gameInfo.variant != VariantNormal) {
5144             // try figure out wild number, as xboard names are not always valid on ICS
5145             for(i=1; i<=36; i++) {
5146               snprintf(buf, MSG_SIZ, "wild/%d", i);
5147                 if(StringToVariant(buf) == gameInfo.variant) break;
5148             }
5149             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5150             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5151             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5152         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5153         SendToICS(ics_prefix);
5154         SendToICS(buf);
5155         if(startedFromSetupPosition || backwardMostMove != 0) {
5156           fen = PositionToFEN(backwardMostMove, NULL);
5157           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5158             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5159             SendToICS(buf);
5160           } else { // FICS: everything has to set by separate bsetup commands
5161             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5162             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5163             SendToICS(buf);
5164             if(!WhiteOnMove(backwardMostMove)) {
5165                 SendToICS("bsetup tomove black\n");
5166             }
5167             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5168             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5169             SendToICS(buf);
5170             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5171             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5172             SendToICS(buf);
5173             i = boards[backwardMostMove][EP_STATUS];
5174             if(i >= 0) { // set e.p.
5175               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5176                 SendToICS(buf);
5177             }
5178             bsetup++;
5179           }
5180         }
5181       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5182             SendToICS("bsetup done\n"); // switch to normal examining.
5183     }
5184     for(i = backwardMostMove; i<last; i++) {
5185         char buf[20];
5186         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5187         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5188             int len = strlen(moveList[i]);
5189             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5190             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5191         }
5192         SendToICS(buf);
5193     }
5194     SendToICS(ics_prefix);
5195     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5196 }
5197
5198 void
5199 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5200 {
5201     if (rf == DROP_RANK) {
5202       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5203       sprintf(move, "%c@%c%c\n",
5204                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5205     } else {
5206         if (promoChar == 'x' || promoChar == NULLCHAR) {
5207           sprintf(move, "%c%c%c%c\n",
5208                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5209         } else {
5210             sprintf(move, "%c%c%c%c%c\n",
5211                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5212         }
5213     }
5214 }
5215
5216 void
5217 ProcessICSInitScript (FILE *f)
5218 {
5219     char buf[MSG_SIZ];
5220
5221     while (fgets(buf, MSG_SIZ, f)) {
5222         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5223     }
5224
5225     fclose(f);
5226 }
5227
5228
5229 static int lastX, lastY, selectFlag, dragging;
5230
5231 void
5232 Sweep (int step)
5233 {
5234     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5235     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5236     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5237     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5238     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5239     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5240     do {
5241         promoSweep -= step;
5242         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5243         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5244         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5245         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5246         if(!step) step = -1;
5247     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5248             appData.testLegality && (promoSweep == king ||
5249             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5250     if(toX >= 0) {
5251         int victim = boards[currentMove][toY][toX];
5252         boards[currentMove][toY][toX] = promoSweep;
5253         DrawPosition(FALSE, boards[currentMove]);
5254         boards[currentMove][toY][toX] = victim;
5255     } else
5256     ChangeDragPiece(promoSweep);
5257 }
5258
5259 int
5260 PromoScroll (int x, int y)
5261 {
5262   int step = 0;
5263
5264   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5265   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5266   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5267   if(!step) return FALSE;
5268   lastX = x; lastY = y;
5269   if((promoSweep < BlackPawn) == flipView) step = -step;
5270   if(step > 0) selectFlag = 1;
5271   if(!selectFlag) Sweep(step);
5272   return FALSE;
5273 }
5274
5275 void
5276 NextPiece (int step)
5277 {
5278     ChessSquare piece = boards[currentMove][toY][toX];
5279     do {
5280         pieceSweep -= step;
5281         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5282         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5283         if(!step) step = -1;
5284     } while(PieceToChar(pieceSweep) == '.');
5285     boards[currentMove][toY][toX] = pieceSweep;
5286     DrawPosition(FALSE, boards[currentMove]);
5287     boards[currentMove][toY][toX] = piece;
5288 }
5289 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5290 void
5291 AlphaRank (char *move, int n)
5292 {
5293 //    char *p = move, c; int x, y;
5294
5295     if (appData.debugMode) {
5296         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5297     }
5298
5299     if(move[1]=='*' &&
5300        move[2]>='0' && move[2]<='9' &&
5301        move[3]>='a' && move[3]<='x'    ) {
5302         move[1] = '@';
5303         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5304         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5305     } else
5306     if(move[0]>='0' && move[0]<='9' &&
5307        move[1]>='a' && move[1]<='x' &&
5308        move[2]>='0' && move[2]<='9' &&
5309        move[3]>='a' && move[3]<='x'    ) {
5310         /* input move, Shogi -> normal */
5311         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5312         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5313         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5314         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5315     } else
5316     if(move[1]=='@' &&
5317        move[3]>='0' && move[3]<='9' &&
5318        move[2]>='a' && move[2]<='x'    ) {
5319         move[1] = '*';
5320         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5321         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5322     } else
5323     if(
5324        move[0]>='a' && move[0]<='x' &&
5325        move[3]>='0' && move[3]<='9' &&
5326        move[2]>='a' && move[2]<='x'    ) {
5327          /* output move, normal -> Shogi */
5328         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5329         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5330         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5331         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5332         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5333     }
5334     if (appData.debugMode) {
5335         fprintf(debugFP, "   out = '%s'\n", move);
5336     }
5337 }
5338
5339 char yy_textstr[8000];
5340
5341 /* Parser for moves from gnuchess, ICS, or user typein box */
5342 Boolean
5343 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5344 {
5345     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5346
5347     switch (*moveType) {
5348       case WhitePromotion:
5349       case BlackPromotion:
5350       case WhiteNonPromotion:
5351       case BlackNonPromotion:
5352       case NormalMove:
5353       case WhiteCapturesEnPassant:
5354       case BlackCapturesEnPassant:
5355       case WhiteKingSideCastle:
5356       case WhiteQueenSideCastle:
5357       case BlackKingSideCastle:
5358       case BlackQueenSideCastle:
5359       case WhiteKingSideCastleWild:
5360       case WhiteQueenSideCastleWild:
5361       case BlackKingSideCastleWild:
5362       case BlackQueenSideCastleWild:
5363       /* Code added by Tord: */
5364       case WhiteHSideCastleFR:
5365       case WhiteASideCastleFR:
5366       case BlackHSideCastleFR:
5367       case BlackASideCastleFR:
5368       /* End of code added by Tord */
5369       case IllegalMove:         /* bug or odd chess variant */
5370         *fromX = currentMoveString[0] - AAA;
5371         *fromY = currentMoveString[1] - ONE;
5372         *toX = currentMoveString[2] - AAA;
5373         *toY = currentMoveString[3] - ONE;
5374         *promoChar = currentMoveString[4];
5375         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5376             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5377     if (appData.debugMode) {
5378         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5379     }
5380             *fromX = *fromY = *toX = *toY = 0;
5381             return FALSE;
5382         }
5383         if (appData.testLegality) {
5384           return (*moveType != IllegalMove);
5385         } else {
5386           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5387                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5388         }
5389
5390       case WhiteDrop:
5391       case BlackDrop:
5392         *fromX = *moveType == WhiteDrop ?
5393           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5394           (int) CharToPiece(ToLower(currentMoveString[0]));
5395         *fromY = DROP_RANK;
5396         *toX = currentMoveString[2] - AAA;
5397         *toY = currentMoveString[3] - ONE;
5398         *promoChar = NULLCHAR;
5399         return TRUE;
5400
5401       case AmbiguousMove:
5402       case ImpossibleMove:
5403       case EndOfFile:
5404       case ElapsedTime:
5405       case Comment:
5406       case PGNTag:
5407       case NAG:
5408       case WhiteWins:
5409       case BlackWins:
5410       case GameIsDrawn:
5411       default:
5412     if (appData.debugMode) {
5413         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5414     }
5415         /* bug? */
5416         *fromX = *fromY = *toX = *toY = 0;
5417         *promoChar = NULLCHAR;
5418         return FALSE;
5419     }
5420 }
5421
5422 Boolean pushed = FALSE;
5423 char *lastParseAttempt;
5424
5425 void
5426 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5427 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5428   int fromX, fromY, toX, toY; char promoChar;
5429   ChessMove moveType;
5430   Boolean valid;
5431   int nr = 0;
5432
5433   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5434   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5435     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5436     pushed = TRUE;
5437   }
5438   endPV = forwardMostMove;
5439   do {
5440     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5441     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5442     lastParseAttempt = pv;
5443     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5444     if(!valid && nr == 0 &&
5445        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5446         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5447         // Hande case where played move is different from leading PV move
5448         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5449         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5450         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5451         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5452           endPV += 2; // if position different, keep this
5453           moveList[endPV-1][0] = fromX + AAA;
5454           moveList[endPV-1][1] = fromY + ONE;
5455           moveList[endPV-1][2] = toX + AAA;
5456           moveList[endPV-1][3] = toY + ONE;
5457           parseList[endPV-1][0] = NULLCHAR;
5458           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5459         }
5460       }
5461     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5462     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5463     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5464     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5465         valid++; // allow comments in PV
5466         continue;
5467     }
5468     nr++;
5469     if(endPV+1 > framePtr) break; // no space, truncate
5470     if(!valid) break;
5471     endPV++;
5472     CopyBoard(boards[endPV], boards[endPV-1]);
5473     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5474     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5475     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5476     CoordsToAlgebraic(boards[endPV - 1],
5477                              PosFlags(endPV - 1),
5478                              fromY, fromX, toY, toX, promoChar,
5479                              parseList[endPV - 1]);
5480   } while(valid);
5481   if(atEnd == 2) return; // used hidden, for PV conversion
5482   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5483   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5484   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5485                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5486   DrawPosition(TRUE, boards[currentMove]);
5487 }
5488
5489 int
5490 MultiPV (ChessProgramState *cps)
5491 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5492         int i;
5493         for(i=0; i<cps->nrOptions; i++)
5494             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5495                 return i;
5496         return -1;
5497 }
5498
5499 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5500
5501 Boolean
5502 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5503 {
5504         int startPV, multi, lineStart, origIndex = index;
5505         char *p, buf2[MSG_SIZ];
5506         ChessProgramState *cps = (pane ? &second : &first);
5507
5508         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5509         lastX = x; lastY = y;
5510         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5511         lineStart = startPV = index;
5512         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5513         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5514         index = startPV;
5515         do{ while(buf[index] && buf[index] != '\n') index++;
5516         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5517         buf[index] = 0;
5518         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5519                 int n = cps->option[multi].value;
5520                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5521                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5522                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5523                 cps->option[multi].value = n;
5524                 *start = *end = 0;
5525                 return FALSE;
5526         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5527                 ExcludeClick(origIndex - lineStart);
5528                 return FALSE;
5529         }
5530         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5531         *start = startPV; *end = index-1;
5532         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5533         return TRUE;
5534 }
5535
5536 char *
5537 PvToSAN (char *pv)
5538 {
5539         static char buf[10*MSG_SIZ];
5540         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5541         *buf = NULLCHAR;
5542         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5543         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5544         for(i = forwardMostMove; i<endPV; i++){
5545             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5546             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5547             k += strlen(buf+k);
5548         }
5549         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5550         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5551         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5552         endPV = savedEnd;
5553         return buf;
5554 }
5555
5556 Boolean
5557 LoadPV (int x, int y)
5558 { // called on right mouse click to load PV
5559   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5560   lastX = x; lastY = y;
5561   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5562   extendGame = FALSE;
5563   return TRUE;
5564 }
5565
5566 void
5567 UnLoadPV ()
5568 {
5569   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5570   if(endPV < 0) return;
5571   if(appData.autoCopyPV) CopyFENToClipboard();
5572   endPV = -1;
5573   if(extendGame && currentMove > forwardMostMove) {
5574         Boolean saveAnimate = appData.animate;
5575         if(pushed) {
5576             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5577                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5578             } else storedGames--; // abandon shelved tail of original game
5579         }
5580         pushed = FALSE;
5581         forwardMostMove = currentMove;
5582         currentMove = oldFMM;
5583         appData.animate = FALSE;
5584         ToNrEvent(forwardMostMove);
5585         appData.animate = saveAnimate;
5586   }
5587   currentMove = forwardMostMove;
5588   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5589   ClearPremoveHighlights();
5590   DrawPosition(TRUE, boards[currentMove]);
5591 }
5592
5593 void
5594 MovePV (int x, int y, int h)
5595 { // step through PV based on mouse coordinates (called on mouse move)
5596   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5597
5598   // we must somehow check if right button is still down (might be released off board!)
5599   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5600   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5601   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5602   if(!step) return;
5603   lastX = x; lastY = y;
5604
5605   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5606   if(endPV < 0) return;
5607   if(y < margin) step = 1; else
5608   if(y > h - margin) step = -1;
5609   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5610   currentMove += step;
5611   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5612   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5613                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5614   DrawPosition(FALSE, boards[currentMove]);
5615 }
5616
5617
5618 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5619 // All positions will have equal probability, but the current method will not provide a unique
5620 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5621 #define DARK 1
5622 #define LITE 2
5623 #define ANY 3
5624
5625 int squaresLeft[4];
5626 int piecesLeft[(int)BlackPawn];
5627 int seed, nrOfShuffles;
5628
5629 void
5630 GetPositionNumber ()
5631 {       // sets global variable seed
5632         int i;
5633
5634         seed = appData.defaultFrcPosition;
5635         if(seed < 0) { // randomize based on time for negative FRC position numbers
5636                 for(i=0; i<50; i++) seed += random();
5637                 seed = random() ^ random() >> 8 ^ random() << 8;
5638                 if(seed<0) seed = -seed;
5639         }
5640 }
5641
5642 int
5643 put (Board board, int pieceType, int rank, int n, int shade)
5644 // put the piece on the (n-1)-th empty squares of the given shade
5645 {
5646         int i;
5647
5648         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5649                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5650                         board[rank][i] = (ChessSquare) pieceType;
5651                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5652                         squaresLeft[ANY]--;
5653                         piecesLeft[pieceType]--;
5654                         return i;
5655                 }
5656         }
5657         return -1;
5658 }
5659
5660
5661 void
5662 AddOnePiece (Board board, int pieceType, int rank, int shade)
5663 // calculate where the next piece goes, (any empty square), and put it there
5664 {
5665         int i;
5666
5667         i = seed % squaresLeft[shade];
5668         nrOfShuffles *= squaresLeft[shade];
5669         seed /= squaresLeft[shade];
5670         put(board, pieceType, rank, i, shade);
5671 }
5672
5673 void
5674 AddTwoPieces (Board board, int pieceType, int rank)
5675 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5676 {
5677         int i, n=squaresLeft[ANY], j=n-1, k;
5678
5679         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5680         i = seed % k;  // pick one
5681         nrOfShuffles *= k;
5682         seed /= k;
5683         while(i >= j) i -= j--;
5684         j = n - 1 - j; i += j;
5685         put(board, pieceType, rank, j, ANY);
5686         put(board, pieceType, rank, i, ANY);
5687 }
5688
5689 void
5690 SetUpShuffle (Board board, int number)
5691 {
5692         int i, p, first=1;
5693
5694         GetPositionNumber(); nrOfShuffles = 1;
5695
5696         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5697         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5698         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5699
5700         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5701
5702         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5703             p = (int) board[0][i];
5704             if(p < (int) BlackPawn) piecesLeft[p] ++;
5705             board[0][i] = EmptySquare;
5706         }
5707
5708         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5709             // shuffles restricted to allow normal castling put KRR first
5710             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5711                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5712             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5713                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5714             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5715                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5716             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5717                 put(board, WhiteRook, 0, 0, ANY);
5718             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5719         }
5720
5721         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5722             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5723             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5724                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5725                 while(piecesLeft[p] >= 2) {
5726                     AddOnePiece(board, p, 0, LITE);
5727                     AddOnePiece(board, p, 0, DARK);
5728                 }
5729                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5730             }
5731
5732         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5733             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5734             // but we leave King and Rooks for last, to possibly obey FRC restriction
5735             if(p == (int)WhiteRook) continue;
5736             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5737             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5738         }
5739
5740         // now everything is placed, except perhaps King (Unicorn) and Rooks
5741
5742         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5743             // Last King gets castling rights
5744             while(piecesLeft[(int)WhiteUnicorn]) {
5745                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5746                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5747             }
5748
5749             while(piecesLeft[(int)WhiteKing]) {
5750                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5751                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5752             }
5753
5754
5755         } else {
5756             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5757             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5758         }
5759
5760         // Only Rooks can be left; simply place them all
5761         while(piecesLeft[(int)WhiteRook]) {
5762                 i = put(board, WhiteRook, 0, 0, ANY);
5763                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5764                         if(first) {
5765                                 first=0;
5766                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5767                         }
5768                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5769                 }
5770         }
5771         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5772             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5773         }
5774
5775         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5776 }
5777
5778 int
5779 SetCharTable (char *table, const char * map)
5780 /* [HGM] moved here from winboard.c because of its general usefulness */
5781 /*       Basically a safe strcpy that uses the last character as King */
5782 {
5783     int result = FALSE; int NrPieces;
5784
5785     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5786                     && NrPieces >= 12 && !(NrPieces&1)) {
5787         int i; /* [HGM] Accept even length from 12 to 34 */
5788
5789         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5790         for( i=0; i<NrPieces/2-1; i++ ) {
5791             table[i] = map[i];
5792             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5793         }
5794         table[(int) WhiteKing]  = map[NrPieces/2-1];
5795         table[(int) BlackKing]  = map[NrPieces-1];
5796
5797         result = TRUE;
5798     }
5799
5800     return result;
5801 }
5802
5803 void
5804 Prelude (Board board)
5805 {       // [HGM] superchess: random selection of exo-pieces
5806         int i, j, k; ChessSquare p;
5807         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5808
5809         GetPositionNumber(); // use FRC position number
5810
5811         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5812             SetCharTable(pieceToChar, appData.pieceToCharTable);
5813             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5814                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5815         }
5816
5817         j = seed%4;                 seed /= 4;
5818         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5819         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5820         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5821         j = seed%3 + (seed%3 >= j); seed /= 3;
5822         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5823         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5824         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5825         j = seed%3;                 seed /= 3;
5826         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5827         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5828         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5829         j = seed%2 + (seed%2 >= j); seed /= 2;
5830         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5831         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5832         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5833         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5834         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5835         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5836         put(board, exoPieces[0],    0, 0, ANY);
5837         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5838 }
5839
5840 void
5841 InitPosition (int redraw)
5842 {
5843     ChessSquare (* pieces)[BOARD_FILES];
5844     int i, j, pawnRow, overrule,
5845     oldx = gameInfo.boardWidth,
5846     oldy = gameInfo.boardHeight,
5847     oldh = gameInfo.holdingsWidth;
5848     static int oldv;
5849
5850     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5851
5852     /* [AS] Initialize pv info list [HGM] and game status */
5853     {
5854         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5855             pvInfoList[i].depth = 0;
5856             boards[i][EP_STATUS] = EP_NONE;
5857             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5858         }
5859
5860         initialRulePlies = 0; /* 50-move counter start */
5861
5862         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5863         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5864     }
5865
5866
5867     /* [HGM] logic here is completely changed. In stead of full positions */
5868     /* the initialized data only consist of the two backranks. The switch */
5869     /* selects which one we will use, which is than copied to the Board   */
5870     /* initialPosition, which for the rest is initialized by Pawns and    */
5871     /* empty squares. This initial position is then copied to boards[0],  */
5872     /* possibly after shuffling, so that it remains available.            */
5873
5874     gameInfo.holdingsWidth = 0; /* default board sizes */
5875     gameInfo.boardWidth    = 8;
5876     gameInfo.boardHeight   = 8;
5877     gameInfo.holdingsSize  = 0;
5878     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5879     for(i=0; i<BOARD_FILES-2; i++)
5880       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5881     initialPosition[EP_STATUS] = EP_NONE;
5882     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5883     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5884          SetCharTable(pieceNickName, appData.pieceNickNames);
5885     else SetCharTable(pieceNickName, "............");
5886     pieces = FIDEArray;
5887
5888     switch (gameInfo.variant) {
5889     case VariantFischeRandom:
5890       shuffleOpenings = TRUE;
5891     default:
5892       break;
5893     case VariantShatranj:
5894       pieces = ShatranjArray;
5895       nrCastlingRights = 0;
5896       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5897       break;
5898     case VariantMakruk:
5899       pieces = makrukArray;
5900       nrCastlingRights = 0;
5901       startedFromSetupPosition = TRUE;
5902       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5903       break;
5904     case VariantTwoKings:
5905       pieces = twoKingsArray;
5906       break;
5907     case VariantGrand:
5908       pieces = GrandArray;
5909       nrCastlingRights = 0;
5910       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5911       gameInfo.boardWidth = 10;
5912       gameInfo.boardHeight = 10;
5913       gameInfo.holdingsSize = 7;
5914       break;
5915     case VariantCapaRandom:
5916       shuffleOpenings = TRUE;
5917     case VariantCapablanca:
5918       pieces = CapablancaArray;
5919       gameInfo.boardWidth = 10;
5920       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5921       break;
5922     case VariantGothic:
5923       pieces = GothicArray;
5924       gameInfo.boardWidth = 10;
5925       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5926       break;
5927     case VariantSChess:
5928       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5929       gameInfo.holdingsSize = 7;
5930       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5931       break;
5932     case VariantJanus:
5933       pieces = JanusArray;
5934       gameInfo.boardWidth = 10;
5935       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5936       nrCastlingRights = 6;
5937         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5938         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5939         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5940         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5941         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5942         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5943       break;
5944     case VariantFalcon:
5945       pieces = FalconArray;
5946       gameInfo.boardWidth = 10;
5947       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5948       break;
5949     case VariantXiangqi:
5950       pieces = XiangqiArray;
5951       gameInfo.boardWidth  = 9;
5952       gameInfo.boardHeight = 10;
5953       nrCastlingRights = 0;
5954       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5955       break;
5956     case VariantShogi:
5957       pieces = ShogiArray;
5958       gameInfo.boardWidth  = 9;
5959       gameInfo.boardHeight = 9;
5960       gameInfo.holdingsSize = 7;
5961       nrCastlingRights = 0;
5962       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5963       break;
5964     case VariantCourier:
5965       pieces = CourierArray;
5966       gameInfo.boardWidth  = 12;
5967       nrCastlingRights = 0;
5968       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5969       break;
5970     case VariantKnightmate:
5971       pieces = KnightmateArray;
5972       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5973       break;
5974     case VariantSpartan:
5975       pieces = SpartanArray;
5976       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5977       break;
5978     case VariantFairy:
5979       pieces = fairyArray;
5980       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5981       break;
5982     case VariantGreat:
5983       pieces = GreatArray;
5984       gameInfo.boardWidth = 10;
5985       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5986       gameInfo.holdingsSize = 8;
5987       break;
5988     case VariantSuper:
5989       pieces = FIDEArray;
5990       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5991       gameInfo.holdingsSize = 8;
5992       startedFromSetupPosition = TRUE;
5993       break;
5994     case VariantCrazyhouse:
5995     case VariantBughouse:
5996       pieces = FIDEArray;
5997       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5998       gameInfo.holdingsSize = 5;
5999       break;
6000     case VariantWildCastle:
6001       pieces = FIDEArray;
6002       /* !!?shuffle with kings guaranteed to be on d or e file */
6003       shuffleOpenings = 1;
6004       break;
6005     case VariantNoCastle:
6006       pieces = FIDEArray;
6007       nrCastlingRights = 0;
6008       /* !!?unconstrained back-rank shuffle */
6009       shuffleOpenings = 1;
6010       break;
6011     }
6012
6013     overrule = 0;
6014     if(appData.NrFiles >= 0) {
6015         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6016         gameInfo.boardWidth = appData.NrFiles;
6017     }
6018     if(appData.NrRanks >= 0) {
6019         gameInfo.boardHeight = appData.NrRanks;
6020     }
6021     if(appData.holdingsSize >= 0) {
6022         i = appData.holdingsSize;
6023         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6024         gameInfo.holdingsSize = i;
6025     }
6026     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6027     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6028         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6029
6030     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6031     if(pawnRow < 1) pawnRow = 1;
6032     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6033
6034     /* User pieceToChar list overrules defaults */
6035     if(appData.pieceToCharTable != NULL)
6036         SetCharTable(pieceToChar, appData.pieceToCharTable);
6037
6038     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6039
6040         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6041             s = (ChessSquare) 0; /* account holding counts in guard band */
6042         for( i=0; i<BOARD_HEIGHT; i++ )
6043             initialPosition[i][j] = s;
6044
6045         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6046         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6047         initialPosition[pawnRow][j] = WhitePawn;
6048         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6049         if(gameInfo.variant == VariantXiangqi) {
6050             if(j&1) {
6051                 initialPosition[pawnRow][j] =
6052                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6053                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6054                    initialPosition[2][j] = WhiteCannon;
6055                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6056                 }
6057             }
6058         }
6059         if(gameInfo.variant == VariantGrand) {
6060             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6061                initialPosition[0][j] = WhiteRook;
6062                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6063             }
6064         }
6065         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6066     }
6067     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6068
6069             j=BOARD_LEFT+1;
6070             initialPosition[1][j] = WhiteBishop;
6071             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6072             j=BOARD_RGHT-2;
6073             initialPosition[1][j] = WhiteRook;
6074             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6075     }
6076
6077     if( nrCastlingRights == -1) {
6078         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6079         /*       This sets default castling rights from none to normal corners   */
6080         /* Variants with other castling rights must set them themselves above    */
6081         nrCastlingRights = 6;
6082
6083         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6084         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6085         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6086         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6087         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6088         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6089      }
6090
6091      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6092      if(gameInfo.variant == VariantGreat) { // promotion commoners
6093         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6094         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6095         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6096         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6097      }
6098      if( gameInfo.variant == VariantSChess ) {
6099       initialPosition[1][0] = BlackMarshall;
6100       initialPosition[2][0] = BlackAngel;
6101       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6102       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6103       initialPosition[1][1] = initialPosition[2][1] =
6104       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6105      }
6106   if (appData.debugMode) {
6107     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6108   }
6109     if(shuffleOpenings) {
6110         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6111         startedFromSetupPosition = TRUE;
6112     }
6113     if(startedFromPositionFile) {
6114       /* [HGM] loadPos: use PositionFile for every new game */
6115       CopyBoard(initialPosition, filePosition);
6116       for(i=0; i<nrCastlingRights; i++)
6117           initialRights[i] = filePosition[CASTLING][i];
6118       startedFromSetupPosition = TRUE;
6119     }
6120
6121     CopyBoard(boards[0], initialPosition);
6122
6123     if(oldx != gameInfo.boardWidth ||
6124        oldy != gameInfo.boardHeight ||
6125        oldv != gameInfo.variant ||
6126        oldh != gameInfo.holdingsWidth
6127                                          )
6128             InitDrawingSizes(-2 ,0);
6129
6130     oldv = gameInfo.variant;
6131     if (redraw)
6132       DrawPosition(TRUE, boards[currentMove]);
6133 }
6134
6135 void
6136 SendBoard (ChessProgramState *cps, int moveNum)
6137 {
6138     char message[MSG_SIZ];
6139
6140     if (cps->useSetboard) {
6141       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6142       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6143       SendToProgram(message, cps);
6144       free(fen);
6145
6146     } else {
6147       ChessSquare *bp;
6148       int i, j, left=0, right=BOARD_WIDTH;
6149       /* Kludge to set black to move, avoiding the troublesome and now
6150        * deprecated "black" command.
6151        */
6152       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6153         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6154
6155       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6156
6157       SendToProgram("edit\n", cps);
6158       SendToProgram("#\n", cps);
6159       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6160         bp = &boards[moveNum][i][left];
6161         for (j = left; j < right; j++, bp++) {
6162           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6163           if ((int) *bp < (int) BlackPawn) {
6164             if(j == BOARD_RGHT+1)
6165                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6166             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6167             if(message[0] == '+' || message[0] == '~') {
6168               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6169                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6170                         AAA + j, ONE + i);
6171             }
6172             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6173                 message[1] = BOARD_RGHT   - 1 - j + '1';
6174                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6175             }
6176             SendToProgram(message, cps);
6177           }
6178         }
6179       }
6180
6181       SendToProgram("c\n", cps);
6182       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6183         bp = &boards[moveNum][i][left];
6184         for (j = left; j < right; j++, bp++) {
6185           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6186           if (((int) *bp != (int) EmptySquare)
6187               && ((int) *bp >= (int) BlackPawn)) {
6188             if(j == BOARD_LEFT-2)
6189                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6190             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6191                     AAA + j, ONE + i);
6192             if(message[0] == '+' || message[0] == '~') {
6193               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6194                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6195                         AAA + j, ONE + i);
6196             }
6197             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6198                 message[1] = BOARD_RGHT   - 1 - j + '1';
6199                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6200             }
6201             SendToProgram(message, cps);
6202           }
6203         }
6204       }
6205
6206       SendToProgram(".\n", cps);
6207     }
6208     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6209 }
6210
6211 char exclusionHeader[MSG_SIZ];
6212 int exCnt, excludePtr;
6213 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6214 static Exclusion excluTab[200];
6215 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6216
6217 static void
6218 WriteMap (int s)
6219 {
6220     int j;
6221     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6222     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6223 }
6224
6225 static void
6226 ClearMap ()
6227 {
6228     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6229     excludePtr = 24; exCnt = 0;
6230     WriteMap(0);
6231 }
6232
6233 static void
6234 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6235 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6236     char buf[2*MOVE_LEN], *p;
6237     Exclusion *e = excluTab;
6238     int i;
6239     for(i=0; i<exCnt; i++)
6240         if(e[i].ff == fromX && e[i].fr == fromY &&
6241            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6242     if(i == exCnt) { // was not in exclude list; add it
6243         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6244         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6245             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6246             return; // abort
6247         }
6248         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6249         excludePtr++; e[i].mark = excludePtr++;
6250         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6251         exCnt++;
6252     }
6253     exclusionHeader[e[i].mark] = state;
6254 }
6255
6256 static int
6257 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6258 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6259     char buf[MSG_SIZ];
6260     int j, k;
6261     ChessMove moveType;
6262     if((signed char)promoChar == -1) { // kludge to indicate best move
6263         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6264             return 1; // if unparsable, abort
6265     }
6266     // update exclusion map (resolving toggle by consulting existing state)
6267     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6268     j = k%8; k >>= 3;
6269     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6270     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6271          excludeMap[k] |=   1<<j;
6272     else excludeMap[k] &= ~(1<<j);
6273     // update header
6274     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6275     // inform engine
6276     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6277     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6278     SendToBoth(buf);
6279     return (state == '+');
6280 }
6281
6282 static void
6283 ExcludeClick (int index)
6284 {
6285     int i, j;
6286     Exclusion *e = excluTab;
6287     if(index < 25) { // none, best or tail clicked
6288         if(index < 13) { // none: include all
6289             WriteMap(0); // clear map
6290             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6291             SendToBoth("include all\n"); // and inform engine
6292         } else if(index > 18) { // tail
6293             if(exclusionHeader[19] == '-') { // tail was excluded
6294                 SendToBoth("include all\n");
6295                 WriteMap(0); // clear map completely
6296                 // now re-exclude selected moves
6297                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6298                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6299             } else { // tail was included or in mixed state
6300                 SendToBoth("exclude all\n");
6301                 WriteMap(0xFF); // fill map completely
6302                 // now re-include selected moves
6303                 j = 0; // count them
6304                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6305                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6306                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6307             }
6308         } else { // best
6309             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6310         }
6311     } else {
6312         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6313             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6314             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6315             break;
6316         }
6317     }
6318 }
6319
6320 ChessSquare
6321 DefaultPromoChoice (int white)
6322 {
6323     ChessSquare result;
6324     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6325         result = WhiteFerz; // no choice
6326     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6327         result= WhiteKing; // in Suicide Q is the last thing we want
6328     else if(gameInfo.variant == VariantSpartan)
6329         result = white ? WhiteQueen : WhiteAngel;
6330     else result = WhiteQueen;
6331     if(!white) result = WHITE_TO_BLACK result;
6332     return result;
6333 }
6334
6335 static int autoQueen; // [HGM] oneclick
6336
6337 int
6338 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6339 {
6340     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6341     /* [HGM] add Shogi promotions */
6342     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6343     ChessSquare piece;
6344     ChessMove moveType;
6345     Boolean premove;
6346
6347     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6348     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6349
6350     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6351       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6352         return FALSE;
6353
6354     piece = boards[currentMove][fromY][fromX];
6355     if(gameInfo.variant == VariantShogi) {
6356         promotionZoneSize = BOARD_HEIGHT/3;
6357         highestPromotingPiece = (int)WhiteFerz;
6358     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6359         promotionZoneSize = 3;
6360     }
6361
6362     // Treat Lance as Pawn when it is not representing Amazon
6363     if(gameInfo.variant != VariantSuper) {
6364         if(piece == WhiteLance) piece = WhitePawn; else
6365         if(piece == BlackLance) piece = BlackPawn;
6366     }
6367
6368     // next weed out all moves that do not touch the promotion zone at all
6369     if((int)piece >= BlackPawn) {
6370         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6371              return FALSE;
6372         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6373     } else {
6374         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6375            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6376     }
6377
6378     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6379
6380     // weed out mandatory Shogi promotions
6381     if(gameInfo.variant == VariantShogi) {
6382         if(piece >= BlackPawn) {
6383             if(toY == 0 && piece == BlackPawn ||
6384                toY == 0 && piece == BlackQueen ||
6385                toY <= 1 && piece == BlackKnight) {
6386                 *promoChoice = '+';
6387                 return FALSE;
6388             }
6389         } else {
6390             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6391                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6392                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6393                 *promoChoice = '+';
6394                 return FALSE;
6395             }
6396         }
6397     }
6398
6399     // weed out obviously illegal Pawn moves
6400     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6401         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6402         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6403         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6404         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6405         // note we are not allowed to test for valid (non-)capture, due to premove
6406     }
6407
6408     // we either have a choice what to promote to, or (in Shogi) whether to promote
6409     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6410         *promoChoice = PieceToChar(BlackFerz);  // no choice
6411         return FALSE;
6412     }
6413     // no sense asking what we must promote to if it is going to explode...
6414     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6415         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6416         return FALSE;
6417     }
6418     // give caller the default choice even if we will not make it
6419     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6420     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6421     if(        sweepSelect && gameInfo.variant != VariantGreat
6422                            && gameInfo.variant != VariantGrand
6423                            && gameInfo.variant != VariantSuper) return FALSE;
6424     if(autoQueen) return FALSE; // predetermined
6425
6426     // suppress promotion popup on illegal moves that are not premoves
6427     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6428               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6429     if(appData.testLegality && !premove) {
6430         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6431                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6432         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6433             return FALSE;
6434     }
6435
6436     return TRUE;
6437 }
6438
6439 int
6440 InPalace (int row, int column)
6441 {   /* [HGM] for Xiangqi */
6442     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6443          column < (BOARD_WIDTH + 4)/2 &&
6444          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6445     return FALSE;
6446 }
6447
6448 int
6449 PieceForSquare (int x, int y)
6450 {
6451   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6452      return -1;
6453   else
6454      return boards[currentMove][y][x];
6455 }
6456
6457 int
6458 OKToStartUserMove (int x, int y)
6459 {
6460     ChessSquare from_piece;
6461     int white_piece;
6462
6463     if (matchMode) return FALSE;
6464     if (gameMode == EditPosition) return TRUE;
6465
6466     if (x >= 0 && y >= 0)
6467       from_piece = boards[currentMove][y][x];
6468     else
6469       from_piece = EmptySquare;
6470
6471     if (from_piece == EmptySquare) return FALSE;
6472
6473     white_piece = (int)from_piece >= (int)WhitePawn &&
6474       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6475
6476     switch (gameMode) {
6477       case AnalyzeFile:
6478       case TwoMachinesPlay:
6479       case EndOfGame:
6480         return FALSE;
6481
6482       case IcsObserving:
6483       case IcsIdle:
6484         return FALSE;
6485
6486       case MachinePlaysWhite:
6487       case IcsPlayingBlack:
6488         if (appData.zippyPlay) return FALSE;
6489         if (white_piece) {
6490             DisplayMoveError(_("You are playing Black"));
6491             return FALSE;
6492         }
6493         break;
6494
6495       case MachinePlaysBlack:
6496       case IcsPlayingWhite:
6497         if (appData.zippyPlay) return FALSE;
6498         if (!white_piece) {
6499             DisplayMoveError(_("You are playing White"));
6500             return FALSE;
6501         }
6502         break;
6503
6504       case PlayFromGameFile:
6505             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6506       case EditGame:
6507         if (!white_piece && WhiteOnMove(currentMove)) {
6508             DisplayMoveError(_("It is White's turn"));
6509             return FALSE;
6510         }
6511         if (white_piece && !WhiteOnMove(currentMove)) {
6512             DisplayMoveError(_("It is Black's turn"));
6513             return FALSE;
6514         }
6515         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6516             /* Editing correspondence game history */
6517             /* Could disallow this or prompt for confirmation */
6518             cmailOldMove = -1;
6519         }
6520         break;
6521
6522       case BeginningOfGame:
6523         if (appData.icsActive) return FALSE;
6524         if (!appData.noChessProgram) {
6525             if (!white_piece) {
6526                 DisplayMoveError(_("You are playing White"));
6527                 return FALSE;
6528             }
6529         }
6530         break;
6531
6532       case Training:
6533         if (!white_piece && WhiteOnMove(currentMove)) {
6534             DisplayMoveError(_("It is White's turn"));
6535             return FALSE;
6536         }
6537         if (white_piece && !WhiteOnMove(currentMove)) {
6538             DisplayMoveError(_("It is Black's turn"));
6539             return FALSE;
6540         }
6541         break;
6542
6543       default:
6544       case IcsExamining:
6545         break;
6546     }
6547     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6548         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6549         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6550         && gameMode != AnalyzeFile && gameMode != Training) {
6551         DisplayMoveError(_("Displayed position is not current"));
6552         return FALSE;
6553     }
6554     return TRUE;
6555 }
6556
6557 Boolean
6558 OnlyMove (int *x, int *y, Boolean captures)
6559 {
6560     DisambiguateClosure cl;
6561     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6562     switch(gameMode) {
6563       case MachinePlaysBlack:
6564       case IcsPlayingWhite:
6565       case BeginningOfGame:
6566         if(!WhiteOnMove(currentMove)) return FALSE;
6567         break;
6568       case MachinePlaysWhite:
6569       case IcsPlayingBlack:
6570         if(WhiteOnMove(currentMove)) return FALSE;
6571         break;
6572       case EditGame:
6573         break;
6574       default:
6575         return FALSE;
6576     }
6577     cl.pieceIn = EmptySquare;
6578     cl.rfIn = *y;
6579     cl.ffIn = *x;
6580     cl.rtIn = -1;
6581     cl.ftIn = -1;
6582     cl.promoCharIn = NULLCHAR;
6583     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6584     if( cl.kind == NormalMove ||
6585         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6586         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6587         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6588       fromX = cl.ff;
6589       fromY = cl.rf;
6590       *x = cl.ft;
6591       *y = cl.rt;
6592       return TRUE;
6593     }
6594     if(cl.kind != ImpossibleMove) return FALSE;
6595     cl.pieceIn = EmptySquare;
6596     cl.rfIn = -1;
6597     cl.ffIn = -1;
6598     cl.rtIn = *y;
6599     cl.ftIn = *x;
6600     cl.promoCharIn = NULLCHAR;
6601     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6602     if( cl.kind == NormalMove ||
6603         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6604         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6605         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6606       fromX = cl.ff;
6607       fromY = cl.rf;
6608       *x = cl.ft;
6609       *y = cl.rt;
6610       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6611       return TRUE;
6612     }
6613     return FALSE;
6614 }
6615
6616 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6617 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6618 int lastLoadGameUseList = FALSE;
6619 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6620 ChessMove lastLoadGameStart = EndOfFile;
6621 int doubleClick;
6622
6623 void
6624 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6625 {
6626     ChessMove moveType;
6627     ChessSquare pup;
6628     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6629
6630     /* Check if the user is playing in turn.  This is complicated because we
6631        let the user "pick up" a piece before it is his turn.  So the piece he
6632        tried to pick up may have been captured by the time he puts it down!
6633        Therefore we use the color the user is supposed to be playing in this
6634        test, not the color of the piece that is currently on the starting
6635        square---except in EditGame mode, where the user is playing both
6636        sides; fortunately there the capture race can't happen.  (It can
6637        now happen in IcsExamining mode, but that's just too bad.  The user
6638        will get a somewhat confusing message in that case.)
6639        */
6640
6641     switch (gameMode) {
6642       case AnalyzeFile:
6643       case TwoMachinesPlay:
6644       case EndOfGame:
6645       case IcsObserving:
6646       case IcsIdle:
6647         /* We switched into a game mode where moves are not accepted,
6648            perhaps while the mouse button was down. */
6649         return;
6650
6651       case MachinePlaysWhite:
6652         /* User is moving for Black */
6653         if (WhiteOnMove(currentMove)) {
6654             DisplayMoveError(_("It is White's turn"));
6655             return;
6656         }
6657         break;
6658
6659       case MachinePlaysBlack:
6660         /* User is moving for White */
6661         if (!WhiteOnMove(currentMove)) {
6662             DisplayMoveError(_("It is Black's turn"));
6663             return;
6664         }
6665         break;
6666
6667       case PlayFromGameFile:
6668             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6669       case EditGame:
6670       case IcsExamining:
6671       case BeginningOfGame:
6672       case AnalyzeMode:
6673       case Training:
6674         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6675         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6676             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6677             /* User is moving for Black */
6678             if (WhiteOnMove(currentMove)) {
6679                 DisplayMoveError(_("It is White's turn"));
6680                 return;
6681             }
6682         } else {
6683             /* User is moving for White */
6684             if (!WhiteOnMove(currentMove)) {
6685                 DisplayMoveError(_("It is Black's turn"));
6686                 return;
6687             }
6688         }
6689         break;
6690
6691       case IcsPlayingBlack:
6692         /* User is moving for Black */
6693         if (WhiteOnMove(currentMove)) {
6694             if (!appData.premove) {
6695                 DisplayMoveError(_("It is White's turn"));
6696             } else if (toX >= 0 && toY >= 0) {
6697                 premoveToX = toX;
6698                 premoveToY = toY;
6699                 premoveFromX = fromX;
6700                 premoveFromY = fromY;
6701                 premovePromoChar = promoChar;
6702                 gotPremove = 1;
6703                 if (appData.debugMode)
6704                     fprintf(debugFP, "Got premove: fromX %d,"
6705                             "fromY %d, toX %d, toY %d\n",
6706                             fromX, fromY, toX, toY);
6707             }
6708             return;
6709         }
6710         break;
6711
6712       case IcsPlayingWhite:
6713         /* User is moving for White */
6714         if (!WhiteOnMove(currentMove)) {
6715             if (!appData.premove) {
6716                 DisplayMoveError(_("It is Black's turn"));
6717             } else if (toX >= 0 && toY >= 0) {
6718                 premoveToX = toX;
6719                 premoveToY = toY;
6720                 premoveFromX = fromX;
6721                 premoveFromY = fromY;
6722                 premovePromoChar = promoChar;
6723                 gotPremove = 1;
6724                 if (appData.debugMode)
6725                     fprintf(debugFP, "Got premove: fromX %d,"
6726                             "fromY %d, toX %d, toY %d\n",
6727                             fromX, fromY, toX, toY);
6728             }
6729             return;
6730         }
6731         break;
6732
6733       default:
6734         break;
6735
6736       case EditPosition:
6737         /* EditPosition, empty square, or different color piece;
6738            click-click move is possible */
6739         if (toX == -2 || toY == -2) {
6740             boards[0][fromY][fromX] = EmptySquare;
6741             DrawPosition(FALSE, boards[currentMove]);
6742             return;
6743         } else if (toX >= 0 && toY >= 0) {
6744             boards[0][toY][toX] = boards[0][fromY][fromX];
6745             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6746                 if(boards[0][fromY][0] != EmptySquare) {
6747                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6748                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6749                 }
6750             } else
6751             if(fromX == BOARD_RGHT+1) {
6752                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6753                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6754                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6755                 }
6756             } else
6757             boards[0][fromY][fromX] = gatingPiece;
6758             DrawPosition(FALSE, boards[currentMove]);
6759             return;
6760         }
6761         return;
6762     }
6763
6764     if(toX < 0 || toY < 0) return;
6765     pup = boards[currentMove][toY][toX];
6766
6767     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6768     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6769          if( pup != EmptySquare ) return;
6770          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6771            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6772                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6773            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6774            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6775            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6776            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6777          fromY = DROP_RANK;
6778     }
6779
6780     /* [HGM] always test for legality, to get promotion info */
6781     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6782                                          fromY, fromX, toY, toX, promoChar);
6783
6784     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6785
6786     /* [HGM] but possibly ignore an IllegalMove result */
6787     if (appData.testLegality) {
6788         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6789             DisplayMoveError(_("Illegal move"));
6790             return;
6791         }
6792     }
6793
6794     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6795         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6796              ClearPremoveHighlights(); // was included
6797         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6798         return;
6799     }
6800
6801     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6802 }
6803
6804 /* Common tail of UserMoveEvent and DropMenuEvent */
6805 int
6806 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6807 {
6808     char *bookHit = 0;
6809
6810     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6811         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6812         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6813         if(WhiteOnMove(currentMove)) {
6814             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6815         } else {
6816             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6817         }
6818     }
6819
6820     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6821        move type in caller when we know the move is a legal promotion */
6822     if(moveType == NormalMove && promoChar)
6823         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6824
6825     /* [HGM] <popupFix> The following if has been moved here from
6826        UserMoveEvent(). Because it seemed to belong here (why not allow
6827        piece drops in training games?), and because it can only be
6828        performed after it is known to what we promote. */
6829     if (gameMode == Training) {
6830       /* compare the move played on the board to the next move in the
6831        * game. If they match, display the move and the opponent's response.
6832        * If they don't match, display an error message.
6833        */
6834       int saveAnimate;
6835       Board testBoard;
6836       CopyBoard(testBoard, boards[currentMove]);
6837       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6838
6839       if (CompareBoards(testBoard, boards[currentMove+1])) {
6840         ForwardInner(currentMove+1);
6841
6842         /* Autoplay the opponent's response.
6843          * if appData.animate was TRUE when Training mode was entered,
6844          * the response will be animated.
6845          */
6846         saveAnimate = appData.animate;
6847         appData.animate = animateTraining;
6848         ForwardInner(currentMove+1);
6849         appData.animate = saveAnimate;
6850
6851         /* check for the end of the game */
6852         if (currentMove >= forwardMostMove) {
6853           gameMode = PlayFromGameFile;
6854           ModeHighlight();
6855           SetTrainingModeOff();
6856           DisplayInformation(_("End of game"));
6857         }
6858       } else {
6859         DisplayError(_("Incorrect move"), 0);
6860       }
6861       return 1;
6862     }
6863
6864   /* Ok, now we know that the move is good, so we can kill
6865      the previous line in Analysis Mode */
6866   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6867                                 && currentMove < forwardMostMove) {
6868     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6869     else forwardMostMove = currentMove;
6870   }
6871
6872   ClearMap();
6873
6874   /* If we need the chess program but it's dead, restart it */
6875   ResurrectChessProgram();
6876
6877   /* A user move restarts a paused game*/
6878   if (pausing)
6879     PauseEvent();
6880
6881   thinkOutput[0] = NULLCHAR;
6882
6883   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6884
6885   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6886     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6887     return 1;
6888   }
6889
6890   if (gameMode == BeginningOfGame) {
6891     if (appData.noChessProgram) {
6892       gameMode = EditGame;
6893       SetGameInfo();
6894     } else {
6895       char buf[MSG_SIZ];
6896       gameMode = MachinePlaysBlack;
6897       StartClocks();
6898       SetGameInfo();
6899       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6900       DisplayTitle(buf);
6901       if (first.sendName) {
6902         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6903         SendToProgram(buf, &first);
6904       }
6905       StartClocks();
6906     }
6907     ModeHighlight();
6908   }
6909
6910   /* Relay move to ICS or chess engine */
6911   if (appData.icsActive) {
6912     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6913         gameMode == IcsExamining) {
6914       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6915         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6916         SendToICS("draw ");
6917         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6918       }
6919       // also send plain move, in case ICS does not understand atomic claims
6920       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6921       ics_user_moved = 1;
6922     }
6923   } else {
6924     if (first.sendTime && (gameMode == BeginningOfGame ||
6925                            gameMode == MachinePlaysWhite ||
6926                            gameMode == MachinePlaysBlack)) {
6927       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6928     }
6929     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6930          // [HGM] book: if program might be playing, let it use book
6931         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6932         first.maybeThinking = TRUE;
6933     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6934         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6935         SendBoard(&first, currentMove+1);
6936         if(second.analyzing) {
6937             if(!second.useSetboard) SendToProgram("undo\n", &second);
6938             SendBoard(&second, currentMove+1);
6939         }
6940     } else {
6941         SendMoveToProgram(forwardMostMove-1, &first);
6942         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6943     }
6944     if (currentMove == cmailOldMove + 1) {
6945       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6946     }
6947   }
6948
6949   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6950
6951   switch (gameMode) {
6952   case EditGame:
6953     if(appData.testLegality)
6954     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6955     case MT_NONE:
6956     case MT_CHECK:
6957       break;
6958     case MT_CHECKMATE:
6959     case MT_STAINMATE:
6960       if (WhiteOnMove(currentMove)) {
6961         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6962       } else {
6963         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6964       }
6965       break;
6966     case MT_STALEMATE:
6967       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6968       break;
6969     }
6970     break;
6971
6972   case MachinePlaysBlack:
6973   case MachinePlaysWhite:
6974     /* disable certain menu options while machine is thinking */
6975     SetMachineThinkingEnables();
6976     break;
6977
6978   default:
6979     break;
6980   }
6981
6982   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6983   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6984
6985   if(bookHit) { // [HGM] book: simulate book reply
6986         static char bookMove[MSG_SIZ]; // a bit generous?
6987
6988         programStats.nodes = programStats.depth = programStats.time =
6989         programStats.score = programStats.got_only_move = 0;
6990         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6991
6992         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6993         strcat(bookMove, bookHit);
6994         HandleMachineMove(bookMove, &first);
6995   }
6996   return 1;
6997 }
6998
6999 void
7000 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7001 {
7002     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7003     Markers *m = (Markers *) closure;
7004     if(rf == fromY && ff == fromX)
7005         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7006                          || kind == WhiteCapturesEnPassant
7007                          || kind == BlackCapturesEnPassant);
7008     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7009 }
7010
7011 void
7012 MarkTargetSquares (int clear)
7013 {
7014   int x, y;
7015   if(clear) // no reason to ever suppress clearing
7016     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7017   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7018      !appData.testLegality || gameMode == EditPosition) return;
7019   if(!clear) {
7020     int capt = 0;
7021     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7022     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7023       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7024       if(capt)
7025       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7026     }
7027   }
7028   DrawPosition(FALSE, NULL);
7029 }
7030
7031 int
7032 Explode (Board board, int fromX, int fromY, int toX, int toY)
7033 {
7034     if(gameInfo.variant == VariantAtomic &&
7035        (board[toY][toX] != EmptySquare ||                     // capture?
7036         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7037                          board[fromY][fromX] == BlackPawn   )
7038       )) {
7039         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7040         return TRUE;
7041     }
7042     return FALSE;
7043 }
7044
7045 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7046
7047 int
7048 CanPromote (ChessSquare piece, int y)
7049 {
7050         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7051         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7052         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7053            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7054            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7055                                                   gameInfo.variant == VariantMakruk) return FALSE;
7056         return (piece == BlackPawn && y == 1 ||
7057                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7058                 piece == BlackLance && y == 1 ||
7059                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7060 }
7061
7062 void
7063 LeftClick (ClickType clickType, int xPix, int yPix)
7064 {
7065     int x, y;
7066     Boolean saveAnimate;
7067     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7068     char promoChoice = NULLCHAR;
7069     ChessSquare piece;
7070     static TimeMark lastClickTime, prevClickTime;
7071
7072     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7073
7074     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7075
7076     if (clickType == Press) ErrorPopDown();
7077
7078     x = EventToSquare(xPix, BOARD_WIDTH);
7079     y = EventToSquare(yPix, BOARD_HEIGHT);
7080     if (!flipView && y >= 0) {
7081         y = BOARD_HEIGHT - 1 - y;
7082     }
7083     if (flipView && x >= 0) {
7084         x = BOARD_WIDTH - 1 - x;
7085     }
7086
7087     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7088         defaultPromoChoice = promoSweep;
7089         promoSweep = EmptySquare;   // terminate sweep
7090         promoDefaultAltered = TRUE;
7091         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7092     }
7093
7094     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7095         if(clickType == Release) return; // ignore upclick of click-click destination
7096         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7097         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7098         if(gameInfo.holdingsWidth &&
7099                 (WhiteOnMove(currentMove)
7100                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7101                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7102             // click in right holdings, for determining promotion piece
7103             ChessSquare p = boards[currentMove][y][x];
7104             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7105             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7106             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7107                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7108                 fromX = fromY = -1;
7109                 return;
7110             }
7111         }
7112         DrawPosition(FALSE, boards[currentMove]);
7113         return;
7114     }
7115
7116     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7117     if(clickType == Press
7118             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7119               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7120               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7121         return;
7122
7123     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7124         // could be static click on premove from-square: abort premove
7125         gotPremove = 0;
7126         ClearPremoveHighlights();
7127     }
7128
7129     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7130         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7131
7132     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7133         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7134                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7135         defaultPromoChoice = DefaultPromoChoice(side);
7136     }
7137
7138     autoQueen = appData.alwaysPromoteToQueen;
7139
7140     if (fromX == -1) {
7141       int originalY = y;
7142       gatingPiece = EmptySquare;
7143       if (clickType != Press) {
7144         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7145             DragPieceEnd(xPix, yPix); dragging = 0;
7146             DrawPosition(FALSE, NULL);
7147         }
7148         return;
7149       }
7150       doubleClick = FALSE;
7151       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7152         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7153       }
7154       fromX = x; fromY = y; toX = toY = -1;
7155       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7156          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7157          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7158             /* First square */
7159             if (OKToStartUserMove(fromX, fromY)) {
7160                 second = 0;
7161                 MarkTargetSquares(0);
7162                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7163                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7164                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7165                     promoSweep = defaultPromoChoice;
7166                     selectFlag = 0; lastX = xPix; lastY = yPix;
7167                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7168                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7169                 }
7170                 if (appData.highlightDragging) {
7171                     SetHighlights(fromX, fromY, -1, -1);
7172                 } else {
7173                     ClearHighlights();
7174                 }
7175             } else fromX = fromY = -1;
7176             return;
7177         }
7178     }
7179
7180     /* fromX != -1 */
7181     if (clickType == Press && gameMode != EditPosition) {
7182         ChessSquare fromP;
7183         ChessSquare toP;
7184         int frc;
7185
7186         // ignore off-board to clicks
7187         if(y < 0 || x < 0) return;
7188
7189         /* Check if clicking again on the same color piece */
7190         fromP = boards[currentMove][fromY][fromX];
7191         toP = boards[currentMove][y][x];
7192         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7193         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7194              WhitePawn <= toP && toP <= WhiteKing &&
7195              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7196              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7197             (BlackPawn <= fromP && fromP <= BlackKing &&
7198              BlackPawn <= toP && toP <= BlackKing &&
7199              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7200              !(fromP == BlackKing && toP == BlackRook && frc))) {
7201             /* Clicked again on same color piece -- changed his mind */
7202             second = (x == fromX && y == fromY);
7203             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7204                 second = FALSE; // first double-click rather than scond click
7205                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7206             }
7207             promoDefaultAltered = FALSE;
7208             MarkTargetSquares(1);
7209            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7210             if (appData.highlightDragging) {
7211                 SetHighlights(x, y, -1, -1);
7212             } else {
7213                 ClearHighlights();
7214             }
7215             if (OKToStartUserMove(x, y)) {
7216                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7217                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7218                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7219                  gatingPiece = boards[currentMove][fromY][fromX];
7220                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7221                 fromX = x;
7222                 fromY = y; dragging = 1;
7223                 MarkTargetSquares(0);
7224                 DragPieceBegin(xPix, yPix, FALSE);
7225                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7226                     promoSweep = defaultPromoChoice;
7227                     selectFlag = 0; lastX = xPix; lastY = yPix;
7228                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7229                 }
7230             }
7231            }
7232            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7233            second = FALSE;
7234         }
7235         // ignore clicks on holdings
7236         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7237     }
7238
7239     if (clickType == Release && x == fromX && y == fromY) {
7240         DragPieceEnd(xPix, yPix); dragging = 0;
7241         if(clearFlag) {
7242             // a deferred attempt to click-click move an empty square on top of a piece
7243             boards[currentMove][y][x] = EmptySquare;
7244             ClearHighlights();
7245             DrawPosition(FALSE, boards[currentMove]);
7246             fromX = fromY = -1; clearFlag = 0;
7247             return;
7248         }
7249         if (appData.animateDragging) {
7250             /* Undo animation damage if any */
7251             DrawPosition(FALSE, NULL);
7252         }
7253         if (second || sweepSelecting) {
7254             /* Second up/down in same square; just abort move */
7255             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7256             second = sweepSelecting = 0;
7257             fromX = fromY = -1;
7258             gatingPiece = EmptySquare;
7259             ClearHighlights();
7260             gotPremove = 0;
7261             ClearPremoveHighlights();
7262         } else {
7263             /* First upclick in same square; start click-click mode */
7264             SetHighlights(x, y, -1, -1);
7265         }
7266         return;
7267     }
7268
7269     clearFlag = 0;
7270
7271     /* we now have a different from- and (possibly off-board) to-square */
7272     /* Completed move */
7273     if(!sweepSelecting) {
7274         toX = x;
7275         toY = y;
7276     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7277
7278     saveAnimate = appData.animate;
7279     if (clickType == Press) {
7280         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7281             // must be Edit Position mode with empty-square selected
7282             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7283             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7284             return;
7285         }
7286         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7287           if(appData.sweepSelect) {
7288             ChessSquare piece = boards[currentMove][fromY][fromX];
7289             promoSweep = defaultPromoChoice;
7290             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7291             selectFlag = 0; lastX = xPix; lastY = yPix;
7292             Sweep(0); // Pawn that is going to promote: preview promotion piece
7293             sweepSelecting = 1;
7294             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7295             MarkTargetSquares(1);
7296           }
7297           return; // promo popup appears on up-click
7298         }
7299         /* Finish clickclick move */
7300         if (appData.animate || appData.highlightLastMove) {
7301             SetHighlights(fromX, fromY, toX, toY);
7302         } else {
7303             ClearHighlights();
7304         }
7305     } else {
7306 #if 0
7307 // [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
7308         /* Finish drag move */
7309         if (appData.highlightLastMove) {
7310             SetHighlights(fromX, fromY, toX, toY);
7311         } else {
7312             ClearHighlights();
7313         }
7314 #endif
7315         DragPieceEnd(xPix, yPix); dragging = 0;
7316         /* Don't animate move and drag both */
7317         appData.animate = FALSE;
7318     }
7319
7320     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7321     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7322         ChessSquare piece = boards[currentMove][fromY][fromX];
7323         if(gameMode == EditPosition && piece != EmptySquare &&
7324            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7325             int n;
7326
7327             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7328                 n = PieceToNumber(piece - (int)BlackPawn);
7329                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7330                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7331                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7332             } else
7333             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7334                 n = PieceToNumber(piece);
7335                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7336                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7337                 boards[currentMove][n][BOARD_WIDTH-2]++;
7338             }
7339             boards[currentMove][fromY][fromX] = EmptySquare;
7340         }
7341         ClearHighlights();
7342         fromX = fromY = -1;
7343         MarkTargetSquares(1);
7344         DrawPosition(TRUE, boards[currentMove]);
7345         return;
7346     }
7347
7348     // off-board moves should not be highlighted
7349     if(x < 0 || y < 0) ClearHighlights();
7350
7351     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7352
7353     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7354         SetHighlights(fromX, fromY, toX, toY);
7355         MarkTargetSquares(1);
7356         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7357             // [HGM] super: promotion to captured piece selected from holdings
7358             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7359             promotionChoice = TRUE;
7360             // kludge follows to temporarily execute move on display, without promoting yet
7361             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7362             boards[currentMove][toY][toX] = p;
7363             DrawPosition(FALSE, boards[currentMove]);
7364             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7365             boards[currentMove][toY][toX] = q;
7366             DisplayMessage("Click in holdings to choose piece", "");
7367             return;
7368         }
7369         PromotionPopUp();
7370     } else {
7371         int oldMove = currentMove;
7372         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7373         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7374         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7375         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7376            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7377             DrawPosition(TRUE, boards[currentMove]);
7378         MarkTargetSquares(1);
7379         fromX = fromY = -1;
7380     }
7381     appData.animate = saveAnimate;
7382     if (appData.animate || appData.animateDragging) {
7383         /* Undo animation damage if needed */
7384         DrawPosition(FALSE, NULL);
7385     }
7386 }
7387
7388 int
7389 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7390 {   // front-end-free part taken out of PieceMenuPopup
7391     int whichMenu; int xSqr, ySqr;
7392
7393     if(seekGraphUp) { // [HGM] seekgraph
7394         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7395         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7396         return -2;
7397     }
7398
7399     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7400          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7401         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7402         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7403         if(action == Press)   {
7404             originalFlip = flipView;
7405             flipView = !flipView; // temporarily flip board to see game from partners perspective
7406             DrawPosition(TRUE, partnerBoard);
7407             DisplayMessage(partnerStatus, "");
7408             partnerUp = TRUE;
7409         } else if(action == Release) {
7410             flipView = originalFlip;
7411             DrawPosition(TRUE, boards[currentMove]);
7412             partnerUp = FALSE;
7413         }
7414         return -2;
7415     }
7416
7417     xSqr = EventToSquare(x, BOARD_WIDTH);
7418     ySqr = EventToSquare(y, BOARD_HEIGHT);
7419     if (action == Release) {
7420         if(pieceSweep != EmptySquare) {
7421             EditPositionMenuEvent(pieceSweep, toX, toY);
7422             pieceSweep = EmptySquare;
7423         } else UnLoadPV(); // [HGM] pv
7424     }
7425     if (action != Press) return -2; // return code to be ignored
7426     switch (gameMode) {
7427       case IcsExamining:
7428         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7429       case EditPosition:
7430         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7431         if (xSqr < 0 || ySqr < 0) return -1;
7432         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7433         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7434         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7435         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7436         NextPiece(0);
7437         return 2; // grab
7438       case IcsObserving:
7439         if(!appData.icsEngineAnalyze) return -1;
7440       case IcsPlayingWhite:
7441       case IcsPlayingBlack:
7442         if(!appData.zippyPlay) goto noZip;
7443       case AnalyzeMode:
7444       case AnalyzeFile:
7445       case MachinePlaysWhite:
7446       case MachinePlaysBlack:
7447       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7448         if (!appData.dropMenu) {
7449           LoadPV(x, y);
7450           return 2; // flag front-end to grab mouse events
7451         }
7452         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7453            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7454       case EditGame:
7455       noZip:
7456         if (xSqr < 0 || ySqr < 0) return -1;
7457         if (!appData.dropMenu || appData.testLegality &&
7458             gameInfo.variant != VariantBughouse &&
7459             gameInfo.variant != VariantCrazyhouse) return -1;
7460         whichMenu = 1; // drop menu
7461         break;
7462       default:
7463         return -1;
7464     }
7465
7466     if (((*fromX = xSqr) < 0) ||
7467         ((*fromY = ySqr) < 0)) {
7468         *fromX = *fromY = -1;
7469         return -1;
7470     }
7471     if (flipView)
7472       *fromX = BOARD_WIDTH - 1 - *fromX;
7473     else
7474       *fromY = BOARD_HEIGHT - 1 - *fromY;
7475
7476     return whichMenu;
7477 }
7478
7479 void
7480 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7481 {
7482 //    char * hint = lastHint;
7483     FrontEndProgramStats stats;
7484
7485     stats.which = cps == &first ? 0 : 1;
7486     stats.depth = cpstats->depth;
7487     stats.nodes = cpstats->nodes;
7488     stats.score = cpstats->score;
7489     stats.time = cpstats->time;
7490     stats.pv = cpstats->movelist;
7491     stats.hint = lastHint;
7492     stats.an_move_index = 0;
7493     stats.an_move_count = 0;
7494
7495     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7496         stats.hint = cpstats->move_name;
7497         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7498         stats.an_move_count = cpstats->nr_moves;
7499     }
7500
7501     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
7502
7503     SetProgramStats( &stats );
7504 }
7505
7506 void
7507 ClearEngineOutputPane (int which)
7508 {
7509     static FrontEndProgramStats dummyStats;
7510     dummyStats.which = which;
7511     dummyStats.pv = "#";
7512     SetProgramStats( &dummyStats );
7513 }
7514
7515 #define MAXPLAYERS 500
7516
7517 char *
7518 TourneyStandings (int display)
7519 {
7520     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7521     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7522     char result, *p, *names[MAXPLAYERS];
7523
7524     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7525         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7526     names[0] = p = strdup(appData.participants);
7527     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7528
7529     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7530
7531     while(result = appData.results[nr]) {
7532         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7533         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7534         wScore = bScore = 0;
7535         switch(result) {
7536           case '+': wScore = 2; break;
7537           case '-': bScore = 2; break;
7538           case '=': wScore = bScore = 1; break;
7539           case ' ':
7540           case '*': return strdup("busy"); // tourney not finished
7541         }
7542         score[w] += wScore;
7543         score[b] += bScore;
7544         games[w]++;
7545         games[b]++;
7546         nr++;
7547     }
7548     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7549     for(w=0; w<nPlayers; w++) {
7550         bScore = -1;
7551         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7552         ranking[w] = b; points[w] = bScore; score[b] = -2;
7553     }
7554     p = malloc(nPlayers*34+1);
7555     for(w=0; w<nPlayers && w<display; w++)
7556         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7557     free(names[0]);
7558     return p;
7559 }
7560
7561 void
7562 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7563 {       // count all piece types
7564         int p, f, r;
7565         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7566         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7567         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7568                 p = board[r][f];
7569                 pCnt[p]++;
7570                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7571                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7572                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7573                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7574                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7575                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7576         }
7577 }
7578
7579 int
7580 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7581 {
7582         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7583         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7584
7585         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7586         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7587         if(myPawns == 2 && nMine == 3) // KPP
7588             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7589         if(myPawns == 1 && nMine == 2) // KP
7590             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7591         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7592             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7593         if(myPawns) return FALSE;
7594         if(pCnt[WhiteRook+side])
7595             return pCnt[BlackRook-side] ||
7596                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7597                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7598                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7599         if(pCnt[WhiteCannon+side]) {
7600             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7601             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7602         }
7603         if(pCnt[WhiteKnight+side])
7604             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7605         return FALSE;
7606 }
7607
7608 int
7609 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7610 {
7611         VariantClass v = gameInfo.variant;
7612
7613         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7614         if(v == VariantShatranj) return TRUE; // always winnable through baring
7615         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7616         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7617
7618         if(v == VariantXiangqi) {
7619                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7620
7621                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7622                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7623                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7624                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7625                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7626                 if(stale) // we have at least one last-rank P plus perhaps C
7627                     return majors // KPKX
7628                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7629                 else // KCA*E*
7630                     return pCnt[WhiteFerz+side] // KCAK
7631                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7632                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7633                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7634
7635         } else if(v == VariantKnightmate) {
7636                 if(nMine == 1) return FALSE;
7637                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7638         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7639                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7640
7641                 if(nMine == 1) return FALSE; // bare King
7642                 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
7643                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7644                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7645                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7646                 if(pCnt[WhiteKnight+side])
7647                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7648                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7649                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7650                 if(nBishops)
7651                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7652                 if(pCnt[WhiteAlfil+side])
7653                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7654                 if(pCnt[WhiteWazir+side])
7655                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7656         }
7657
7658         return TRUE;
7659 }
7660
7661 int
7662 CompareWithRights (Board b1, Board b2)
7663 {
7664     int rights = 0;
7665     if(!CompareBoards(b1, b2)) return FALSE;
7666     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7667     /* compare castling rights */
7668     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7669            rights++; /* King lost rights, while rook still had them */
7670     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7671         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7672            rights++; /* but at least one rook lost them */
7673     }
7674     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7675            rights++;
7676     if( b1[CASTLING][5] != NoRights ) {
7677         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7678            rights++;
7679     }
7680     return rights == 0;
7681 }
7682
7683 int
7684 Adjudicate (ChessProgramState *cps)
7685 {       // [HGM] some adjudications useful with buggy engines
7686         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7687         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7688         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7689         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7690         int k, drop, count = 0; static int bare = 1;
7691         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7692         Boolean canAdjudicate = !appData.icsActive;
7693
7694         // most tests only when we understand the game, i.e. legality-checking on
7695             if( appData.testLegality )
7696             {   /* [HGM] Some more adjudications for obstinate engines */
7697                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7698                 static int moveCount = 6;
7699                 ChessMove result;
7700                 char *reason = NULL;
7701
7702                 /* Count what is on board. */
7703                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7704
7705                 /* Some material-based adjudications that have to be made before stalemate test */
7706                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7707                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7708                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7709                      if(canAdjudicate && appData.checkMates) {
7710                          if(engineOpponent)
7711                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7712                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7713                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7714                          return 1;
7715                      }
7716                 }
7717
7718                 /* Bare King in Shatranj (loses) or Losers (wins) */
7719                 if( nrW == 1 || nrB == 1) {
7720                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7721                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7722                      if(canAdjudicate && appData.checkMates) {
7723                          if(engineOpponent)
7724                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7725                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7726                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7727                          return 1;
7728                      }
7729                   } else
7730                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7731                   {    /* bare King */
7732                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7733                         if(canAdjudicate && appData.checkMates) {
7734                             /* but only adjudicate if adjudication enabled */
7735                             if(engineOpponent)
7736                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7737                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7738                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7739                             return 1;
7740                         }
7741                   }
7742                 } else bare = 1;
7743
7744
7745             // don't wait for engine to announce game end if we can judge ourselves
7746             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7747               case MT_CHECK:
7748                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7749                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7750                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7751                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7752                             checkCnt++;
7753                         if(checkCnt >= 2) {
7754                             reason = "Xboard adjudication: 3rd check";
7755                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7756                             break;
7757                         }
7758                     }
7759                 }
7760               case MT_NONE:
7761               default:
7762                 break;
7763               case MT_STALEMATE:
7764               case MT_STAINMATE:
7765                 reason = "Xboard adjudication: Stalemate";
7766                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7767                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7768                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7769                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7770                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7771                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7772                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7773                                                                         EP_CHECKMATE : EP_WINS);
7774                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7775                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7776                 }
7777                 break;
7778               case MT_CHECKMATE:
7779                 reason = "Xboard adjudication: Checkmate";
7780                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7781                 if(gameInfo.variant == VariantShogi) {
7782                     if(forwardMostMove > backwardMostMove
7783                        && moveList[forwardMostMove-1][1] == '@'
7784                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7785                         reason = "XBoard adjudication: pawn-drop mate";
7786                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7787                     }
7788                 }
7789                 break;
7790             }
7791
7792                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7793                     case EP_STALEMATE:
7794                         result = GameIsDrawn; break;
7795                     case EP_CHECKMATE:
7796                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7797                     case EP_WINS:
7798                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7799                     default:
7800                         result = EndOfFile;
7801                 }
7802                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7803                     if(engineOpponent)
7804                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7805                     GameEnds( result, reason, GE_XBOARD );
7806                     return 1;
7807                 }
7808
7809                 /* Next absolutely insufficient mating material. */
7810                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7811                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7812                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7813
7814                      /* always flag draws, for judging claims */
7815                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7816
7817                      if(canAdjudicate && appData.materialDraws) {
7818                          /* but only adjudicate them if adjudication enabled */
7819                          if(engineOpponent) {
7820                            SendToProgram("force\n", engineOpponent); // suppress reply
7821                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7822                          }
7823                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7824                          return 1;
7825                      }
7826                 }
7827
7828                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7829                 if(gameInfo.variant == VariantXiangqi ?
7830                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7831                  : nrW + nrB == 4 &&
7832                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7833                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7834                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7835                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7836                    ) ) {
7837                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7838                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7839                           if(engineOpponent) {
7840                             SendToProgram("force\n", engineOpponent); // suppress reply
7841                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7842                           }
7843                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7844                           return 1;
7845                      }
7846                 } else moveCount = 6;
7847             }
7848
7849         // Repetition draws and 50-move rule can be applied independently of legality testing
7850
7851                 /* Check for rep-draws */
7852                 count = 0;
7853                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7854                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7855                 for(k = forwardMostMove-2;
7856                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7857                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7858                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7859                     k-=2)
7860                 {   int rights=0;
7861                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7862                         /* compare castling rights */
7863                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7864                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7865                                 rights++; /* King lost rights, while rook still had them */
7866                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7867                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7868                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7869                                    rights++; /* but at least one rook lost them */
7870                         }
7871                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7872                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7873                                 rights++;
7874                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7875                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7876                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7877                                    rights++;
7878                         }
7879                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7880                             && appData.drawRepeats > 1) {
7881                              /* adjudicate after user-specified nr of repeats */
7882                              int result = GameIsDrawn;
7883                              char *details = "XBoard adjudication: repetition draw";
7884                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7885                                 // [HGM] xiangqi: check for forbidden perpetuals
7886                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7887                                 for(m=forwardMostMove; m>k; m-=2) {
7888                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7889                                         ourPerpetual = 0; // the current mover did not always check
7890                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7891                                         hisPerpetual = 0; // the opponent did not always check
7892                                 }
7893                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7894                                                                         ourPerpetual, hisPerpetual);
7895                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7896                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7897                                     details = "Xboard adjudication: perpetual checking";
7898                                 } else
7899                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7900                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7901                                 } else
7902                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7903                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7904                                         result = BlackWins;
7905                                         details = "Xboard adjudication: repetition";
7906                                     }
7907                                 } else // it must be XQ
7908                                 // Now check for perpetual chases
7909                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7910                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7911                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7912                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7913                                         static char resdet[MSG_SIZ];
7914                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7915                                         details = resdet;
7916                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7917                                     } else
7918                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7919                                         break; // Abort repetition-checking loop.
7920                                 }
7921                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7922                              }
7923                              if(engineOpponent) {
7924                                SendToProgram("force\n", engineOpponent); // suppress reply
7925                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7926                              }
7927                              GameEnds( result, details, GE_XBOARD );
7928                              return 1;
7929                         }
7930                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7931                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7932                     }
7933                 }
7934
7935                 /* Now we test for 50-move draws. Determine ply count */
7936                 count = forwardMostMove;
7937                 /* look for last irreversble move */
7938                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7939                     count--;
7940                 /* if we hit starting position, add initial plies */
7941                 if( count == backwardMostMove )
7942                     count -= initialRulePlies;
7943                 count = forwardMostMove - count;
7944                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7945                         // adjust reversible move counter for checks in Xiangqi
7946                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7947                         if(i < backwardMostMove) i = backwardMostMove;
7948                         while(i <= forwardMostMove) {
7949                                 lastCheck = inCheck; // check evasion does not count
7950                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7951                                 if(inCheck || lastCheck) count--; // check does not count
7952                                 i++;
7953                         }
7954                 }
7955                 if( count >= 100)
7956                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7957                          /* this is used to judge if draw claims are legal */
7958                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7959                          if(engineOpponent) {
7960                            SendToProgram("force\n", engineOpponent); // suppress reply
7961                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7962                          }
7963                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7964                          return 1;
7965                 }
7966
7967                 /* if draw offer is pending, treat it as a draw claim
7968                  * when draw condition present, to allow engines a way to
7969                  * claim draws before making their move to avoid a race
7970                  * condition occurring after their move
7971                  */
7972                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7973                          char *p = NULL;
7974                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7975                              p = "Draw claim: 50-move rule";
7976                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7977                              p = "Draw claim: 3-fold repetition";
7978                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7979                              p = "Draw claim: insufficient mating material";
7980                          if( p != NULL && canAdjudicate) {
7981                              if(engineOpponent) {
7982                                SendToProgram("force\n", engineOpponent); // suppress reply
7983                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7984                              }
7985                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7986                              return 1;
7987                          }
7988                 }
7989
7990                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7991                     if(engineOpponent) {
7992                       SendToProgram("force\n", engineOpponent); // suppress reply
7993                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7994                     }
7995                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7996                     return 1;
7997                 }
7998         return 0;
7999 }
8000
8001 char *
8002 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8003 {   // [HGM] book: this routine intercepts moves to simulate book replies
8004     char *bookHit = NULL;
8005
8006     //first determine if the incoming move brings opponent into his book
8007     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8008         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8009     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8010     if(bookHit != NULL && !cps->bookSuspend) {
8011         // make sure opponent is not going to reply after receiving move to book position
8012         SendToProgram("force\n", cps);
8013         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8014     }
8015     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8016     // now arrange restart after book miss
8017     if(bookHit) {
8018         // after a book hit we never send 'go', and the code after the call to this routine
8019         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8020         char buf[MSG_SIZ], *move = bookHit;
8021         if(cps->useSAN) {
8022             int fromX, fromY, toX, toY;
8023             char promoChar;
8024             ChessMove moveType;
8025             move = buf + 30;
8026             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8027                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8028                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8029                                     PosFlags(forwardMostMove),
8030                                     fromY, fromX, toY, toX, promoChar, move);
8031             } else {
8032                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8033                 bookHit = NULL;
8034             }
8035         }
8036         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8037         SendToProgram(buf, cps);
8038         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8039     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8040         SendToProgram("go\n", cps);
8041         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8042     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8043         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8044             SendToProgram("go\n", cps);
8045         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8046     }
8047     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8048 }
8049
8050 int
8051 LoadError (char *errmess, ChessProgramState *cps)
8052 {   // unloads engine and switches back to -ncp mode if it was first
8053     if(cps->initDone) return FALSE;
8054     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8055     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8056     cps->pr = NoProc;
8057     if(cps == &first) {
8058         appData.noChessProgram = TRUE;
8059         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8060         gameMode = BeginningOfGame; ModeHighlight();
8061         SetNCPMode();
8062     }
8063     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8064     DisplayMessage("", ""); // erase waiting message
8065     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8066     return TRUE;
8067 }
8068
8069 char *savedMessage;
8070 ChessProgramState *savedState;
8071 void
8072 DeferredBookMove (void)
8073 {
8074         if(savedState->lastPing != savedState->lastPong)
8075                     ScheduleDelayedEvent(DeferredBookMove, 10);
8076         else
8077         HandleMachineMove(savedMessage, savedState);
8078 }
8079
8080 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8081 static ChessProgramState *stalledEngine;
8082 static char stashedInputMove[MSG_SIZ];
8083
8084 void
8085 HandleMachineMove (char *message, ChessProgramState *cps)
8086 {
8087     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8088     char realname[MSG_SIZ];
8089     int fromX, fromY, toX, toY;
8090     ChessMove moveType;
8091     char promoChar;
8092     char *p, *pv=buf1;
8093     int machineWhite, oldError;
8094     char *bookHit;
8095
8096     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8097         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8098         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8099             DisplayError(_("Invalid pairing from pairing engine"), 0);
8100             return;
8101         }
8102         pairingReceived = 1;
8103         NextMatchGame();
8104         return; // Skim the pairing messages here.
8105     }
8106
8107     oldError = cps->userError; cps->userError = 0;
8108
8109 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8110     /*
8111      * Kludge to ignore BEL characters
8112      */
8113     while (*message == '\007') message++;
8114
8115     /*
8116      * [HGM] engine debug message: ignore lines starting with '#' character
8117      */
8118     if(cps->debug && *message == '#') return;
8119
8120     /*
8121      * Look for book output
8122      */
8123     if (cps == &first && bookRequested) {
8124         if (message[0] == '\t' || message[0] == ' ') {
8125             /* Part of the book output is here; append it */
8126             strcat(bookOutput, message);
8127             strcat(bookOutput, "  \n");
8128             return;
8129         } else if (bookOutput[0] != NULLCHAR) {
8130             /* All of book output has arrived; display it */
8131             char *p = bookOutput;
8132             while (*p != NULLCHAR) {
8133                 if (*p == '\t') *p = ' ';
8134                 p++;
8135             }
8136             DisplayInformation(bookOutput);
8137             bookRequested = FALSE;
8138             /* Fall through to parse the current output */
8139         }
8140     }
8141
8142     /*
8143      * Look for machine move.
8144      */
8145     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8146         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8147     {
8148         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8149             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8150             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8151             stalledEngine = cps;
8152             if(appData.ponderNextMove) { // bring opponent out of ponder
8153                 if(gameMode == TwoMachinesPlay) {
8154                     if(cps->other->pause)
8155                         PauseEngine(cps->other);
8156                     else
8157                         SendToProgram("easy\n", cps->other);
8158                 }
8159             }
8160             StopClocks();
8161             return;
8162         }
8163
8164         /* This method is only useful on engines that support ping */
8165         if (cps->lastPing != cps->lastPong) {
8166           if (gameMode == BeginningOfGame) {
8167             /* Extra move from before last new; ignore */
8168             if (appData.debugMode) {
8169                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8170             }
8171           } else {
8172             if (appData.debugMode) {
8173                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8174                         cps->which, gameMode);
8175             }
8176
8177             SendToProgram("undo\n", cps);
8178           }
8179           return;
8180         }
8181
8182         switch (gameMode) {
8183           case BeginningOfGame:
8184             /* Extra move from before last reset; ignore */
8185             if (appData.debugMode) {
8186                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8187             }
8188             return;
8189
8190           case EndOfGame:
8191           case IcsIdle:
8192           default:
8193             /* Extra move after we tried to stop.  The mode test is
8194                not a reliable way of detecting this problem, but it's
8195                the best we can do on engines that don't support ping.
8196             */
8197             if (appData.debugMode) {
8198                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8199                         cps->which, gameMode);
8200             }
8201             SendToProgram("undo\n", cps);
8202             return;
8203
8204           case MachinePlaysWhite:
8205           case IcsPlayingWhite:
8206             machineWhite = TRUE;
8207             break;
8208
8209           case MachinePlaysBlack:
8210           case IcsPlayingBlack:
8211             machineWhite = FALSE;
8212             break;
8213
8214           case TwoMachinesPlay:
8215             machineWhite = (cps->twoMachinesColor[0] == 'w');
8216             break;
8217         }
8218         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8219             if (appData.debugMode) {
8220                 fprintf(debugFP,
8221                         "Ignoring move out of turn by %s, gameMode %d"
8222                         ", forwardMost %d\n",
8223                         cps->which, gameMode, forwardMostMove);
8224             }
8225             return;
8226         }
8227
8228         if(cps->alphaRank) AlphaRank(machineMove, 4);
8229         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8230                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8231             /* Machine move could not be parsed; ignore it. */
8232           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8233                     machineMove, _(cps->which));
8234             DisplayMoveError(buf1);
8235             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8236                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8237             if (gameMode == TwoMachinesPlay) {
8238               GameEnds(machineWhite ? BlackWins : WhiteWins,
8239                        buf1, GE_XBOARD);
8240             }
8241             return;
8242         }
8243
8244         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8245         /* So we have to redo legality test with true e.p. status here,  */
8246         /* to make sure an illegal e.p. capture does not slip through,   */
8247         /* to cause a forfeit on a justified illegal-move complaint      */
8248         /* of the opponent.                                              */
8249         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8250            ChessMove moveType;
8251            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8252                              fromY, fromX, toY, toX, promoChar);
8253             if(moveType == IllegalMove) {
8254               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8255                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8256                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8257                            buf1, GE_XBOARD);
8258                 return;
8259            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8260            /* [HGM] Kludge to handle engines that send FRC-style castling
8261               when they shouldn't (like TSCP-Gothic) */
8262            switch(moveType) {
8263              case WhiteASideCastleFR:
8264              case BlackASideCastleFR:
8265                toX+=2;
8266                currentMoveString[2]++;
8267                break;
8268              case WhiteHSideCastleFR:
8269              case BlackHSideCastleFR:
8270                toX--;
8271                currentMoveString[2]--;
8272                break;
8273              default: ; // nothing to do, but suppresses warning of pedantic compilers
8274            }
8275         }
8276         hintRequested = FALSE;
8277         lastHint[0] = NULLCHAR;
8278         bookRequested = FALSE;
8279         /* Program may be pondering now */
8280         cps->maybeThinking = TRUE;
8281         if (cps->sendTime == 2) cps->sendTime = 1;
8282         if (cps->offeredDraw) cps->offeredDraw--;
8283
8284         /* [AS] Save move info*/
8285         pvInfoList[ forwardMostMove ].score = programStats.score;
8286         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8287         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8288
8289         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8290
8291         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8292         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8293             int count = 0;
8294
8295             while( count < adjudicateLossPlies ) {
8296                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8297
8298                 if( count & 1 ) {
8299                     score = -score; /* Flip score for winning side */
8300                 }
8301
8302                 if( score > adjudicateLossThreshold ) {
8303                     break;
8304                 }
8305
8306                 count++;
8307             }
8308
8309             if( count >= adjudicateLossPlies ) {
8310                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8311
8312                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8313                     "Xboard adjudication",
8314                     GE_XBOARD );
8315
8316                 return;
8317             }
8318         }
8319
8320         if(Adjudicate(cps)) {
8321             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8322             return; // [HGM] adjudicate: for all automatic game ends
8323         }
8324
8325 #if ZIPPY
8326         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8327             first.initDone) {
8328           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8329                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8330                 SendToICS("draw ");
8331                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8332           }
8333           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8334           ics_user_moved = 1;
8335           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8336                 char buf[3*MSG_SIZ];
8337
8338                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8339                         programStats.score / 100.,
8340                         programStats.depth,
8341                         programStats.time / 100.,
8342                         (unsigned int)programStats.nodes,
8343                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8344                         programStats.movelist);
8345                 SendToICS(buf);
8346 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8347           }
8348         }
8349 #endif
8350
8351         /* [AS] Clear stats for next move */
8352         ClearProgramStats();
8353         thinkOutput[0] = NULLCHAR;
8354         hiddenThinkOutputState = 0;
8355
8356         bookHit = NULL;
8357         if (gameMode == TwoMachinesPlay) {
8358             /* [HGM] relaying draw offers moved to after reception of move */
8359             /* and interpreting offer as claim if it brings draw condition */
8360             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8361                 SendToProgram("draw\n", cps->other);
8362             }
8363             if (cps->other->sendTime) {
8364                 SendTimeRemaining(cps->other,
8365                                   cps->other->twoMachinesColor[0] == 'w');
8366             }
8367             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8368             if (firstMove && !bookHit) {
8369                 firstMove = FALSE;
8370                 if (cps->other->useColors) {
8371                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8372                 }
8373                 SendToProgram("go\n", cps->other);
8374             }
8375             cps->other->maybeThinking = TRUE;
8376         }
8377
8378         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8379
8380         if (!pausing && appData.ringBellAfterMoves) {
8381             RingBell();
8382         }
8383
8384         /*
8385          * Reenable menu items that were disabled while
8386          * machine was thinking
8387          */
8388         if (gameMode != TwoMachinesPlay)
8389             SetUserThinkingEnables();
8390
8391         // [HGM] book: after book hit opponent has received move and is now in force mode
8392         // force the book reply into it, and then fake that it outputted this move by jumping
8393         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8394         if(bookHit) {
8395                 static char bookMove[MSG_SIZ]; // a bit generous?
8396
8397                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8398                 strcat(bookMove, bookHit);
8399                 message = bookMove;
8400                 cps = cps->other;
8401                 programStats.nodes = programStats.depth = programStats.time =
8402                 programStats.score = programStats.got_only_move = 0;
8403                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8404
8405                 if(cps->lastPing != cps->lastPong) {
8406                     savedMessage = message; // args for deferred call
8407                     savedState = cps;
8408                     ScheduleDelayedEvent(DeferredBookMove, 10);
8409                     return;
8410                 }
8411                 goto FakeBookMove;
8412         }
8413
8414         return;
8415     }
8416
8417     /* Set special modes for chess engines.  Later something general
8418      *  could be added here; for now there is just one kludge feature,
8419      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8420      *  when "xboard" is given as an interactive command.
8421      */
8422     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8423         cps->useSigint = FALSE;
8424         cps->useSigterm = FALSE;
8425     }
8426     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8427       ParseFeatures(message+8, cps);
8428       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8429     }
8430
8431     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8432                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8433       int dummy, s=6; char buf[MSG_SIZ];
8434       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8435       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8436       if(startedFromSetupPosition) return;
8437       ParseFEN(boards[0], &dummy, message+s);
8438       DrawPosition(TRUE, boards[0]);
8439       startedFromSetupPosition = TRUE;
8440       return;
8441     }
8442     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8443      * want this, I was asked to put it in, and obliged.
8444      */
8445     if (!strncmp(message, "setboard ", 9)) {
8446         Board initial_position;
8447
8448         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8449
8450         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8451             DisplayError(_("Bad FEN received from engine"), 0);
8452             return ;
8453         } else {
8454            Reset(TRUE, FALSE);
8455            CopyBoard(boards[0], initial_position);
8456            initialRulePlies = FENrulePlies;
8457            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8458            else gameMode = MachinePlaysBlack;
8459            DrawPosition(FALSE, boards[currentMove]);
8460         }
8461         return;
8462     }
8463
8464     /*
8465      * Look for communication commands
8466      */
8467     if (!strncmp(message, "telluser ", 9)) {
8468         if(message[9] == '\\' && message[10] == '\\')
8469             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8470         PlayTellSound();
8471         DisplayNote(message + 9);
8472         return;
8473     }
8474     if (!strncmp(message, "tellusererror ", 14)) {
8475         cps->userError = 1;
8476         if(message[14] == '\\' && message[15] == '\\')
8477             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8478         PlayTellSound();
8479         DisplayError(message + 14, 0);
8480         return;
8481     }
8482     if (!strncmp(message, "tellopponent ", 13)) {
8483       if (appData.icsActive) {
8484         if (loggedOn) {
8485           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8486           SendToICS(buf1);
8487         }
8488       } else {
8489         DisplayNote(message + 13);
8490       }
8491       return;
8492     }
8493     if (!strncmp(message, "tellothers ", 11)) {
8494       if (appData.icsActive) {
8495         if (loggedOn) {
8496           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8497           SendToICS(buf1);
8498         }
8499       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8500       return;
8501     }
8502     if (!strncmp(message, "tellall ", 8)) {
8503       if (appData.icsActive) {
8504         if (loggedOn) {
8505           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8506           SendToICS(buf1);
8507         }
8508       } else {
8509         DisplayNote(message + 8);
8510       }
8511       return;
8512     }
8513     if (strncmp(message, "warning", 7) == 0) {
8514         /* Undocumented feature, use tellusererror in new code */
8515         DisplayError(message, 0);
8516         return;
8517     }
8518     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8519         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8520         strcat(realname, " query");
8521         AskQuestion(realname, buf2, buf1, cps->pr);
8522         return;
8523     }
8524     /* Commands from the engine directly to ICS.  We don't allow these to be
8525      *  sent until we are logged on. Crafty kibitzes have been known to
8526      *  interfere with the login process.
8527      */
8528     if (loggedOn) {
8529         if (!strncmp(message, "tellics ", 8)) {
8530             SendToICS(message + 8);
8531             SendToICS("\n");
8532             return;
8533         }
8534         if (!strncmp(message, "tellicsnoalias ", 15)) {
8535             SendToICS(ics_prefix);
8536             SendToICS(message + 15);
8537             SendToICS("\n");
8538             return;
8539         }
8540         /* The following are for backward compatibility only */
8541         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8542             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8543             SendToICS(ics_prefix);
8544             SendToICS(message);
8545             SendToICS("\n");
8546             return;
8547         }
8548     }
8549     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8550         return;
8551     }
8552     /*
8553      * If the move is illegal, cancel it and redraw the board.
8554      * Also deal with other error cases.  Matching is rather loose
8555      * here to accommodate engines written before the spec.
8556      */
8557     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8558         strncmp(message, "Error", 5) == 0) {
8559         if (StrStr(message, "name") ||
8560             StrStr(message, "rating") || StrStr(message, "?") ||
8561             StrStr(message, "result") || StrStr(message, "board") ||
8562             StrStr(message, "bk") || StrStr(message, "computer") ||
8563             StrStr(message, "variant") || StrStr(message, "hint") ||
8564             StrStr(message, "random") || StrStr(message, "depth") ||
8565             StrStr(message, "accepted")) {
8566             return;
8567         }
8568         if (StrStr(message, "protover")) {
8569           /* Program is responding to input, so it's apparently done
8570              initializing, and this error message indicates it is
8571              protocol version 1.  So we don't need to wait any longer
8572              for it to initialize and send feature commands. */
8573           FeatureDone(cps, 1);
8574           cps->protocolVersion = 1;
8575           return;
8576         }
8577         cps->maybeThinking = FALSE;
8578
8579         if (StrStr(message, "draw")) {
8580             /* Program doesn't have "draw" command */
8581             cps->sendDrawOffers = 0;
8582             return;
8583         }
8584         if (cps->sendTime != 1 &&
8585             (StrStr(message, "time") || StrStr(message, "otim"))) {
8586           /* Program apparently doesn't have "time" or "otim" command */
8587           cps->sendTime = 0;
8588           return;
8589         }
8590         if (StrStr(message, "analyze")) {
8591             cps->analysisSupport = FALSE;
8592             cps->analyzing = FALSE;
8593 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8594             EditGameEvent(); // [HGM] try to preserve loaded game
8595             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8596             DisplayError(buf2, 0);
8597             return;
8598         }
8599         if (StrStr(message, "(no matching move)st")) {
8600           /* Special kludge for GNU Chess 4 only */
8601           cps->stKludge = TRUE;
8602           SendTimeControl(cps, movesPerSession, timeControl,
8603                           timeIncrement, appData.searchDepth,
8604                           searchTime);
8605           return;
8606         }
8607         if (StrStr(message, "(no matching move)sd")) {
8608           /* Special kludge for GNU Chess 4 only */
8609           cps->sdKludge = TRUE;
8610           SendTimeControl(cps, movesPerSession, timeControl,
8611                           timeIncrement, appData.searchDepth,
8612                           searchTime);
8613           return;
8614         }
8615         if (!StrStr(message, "llegal")) {
8616             return;
8617         }
8618         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8619             gameMode == IcsIdle) return;
8620         if (forwardMostMove <= backwardMostMove) return;
8621         if (pausing) PauseEvent();
8622       if(appData.forceIllegal) {
8623             // [HGM] illegal: machine refused move; force position after move into it
8624           SendToProgram("force\n", cps);
8625           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8626                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8627                 // when black is to move, while there might be nothing on a2 or black
8628                 // might already have the move. So send the board as if white has the move.
8629                 // But first we must change the stm of the engine, as it refused the last move
8630                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8631                 if(WhiteOnMove(forwardMostMove)) {
8632                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8633                     SendBoard(cps, forwardMostMove); // kludgeless board
8634                 } else {
8635                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8636                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8637                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8638                 }
8639           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8640             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8641                  gameMode == TwoMachinesPlay)
8642               SendToProgram("go\n", cps);
8643             return;
8644       } else
8645         if (gameMode == PlayFromGameFile) {
8646             /* Stop reading this game file */
8647             gameMode = EditGame;
8648             ModeHighlight();
8649         }
8650         /* [HGM] illegal-move claim should forfeit game when Xboard */
8651         /* only passes fully legal moves                            */
8652         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8653             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8654                                 "False illegal-move claim", GE_XBOARD );
8655             return; // do not take back move we tested as valid
8656         }
8657         currentMove = forwardMostMove-1;
8658         DisplayMove(currentMove-1); /* before DisplayMoveError */
8659         SwitchClocks(forwardMostMove-1); // [HGM] race
8660         DisplayBothClocks();
8661         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8662                 parseList[currentMove], _(cps->which));
8663         DisplayMoveError(buf1);
8664         DrawPosition(FALSE, boards[currentMove]);
8665
8666         SetUserThinkingEnables();
8667         return;
8668     }
8669     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8670         /* Program has a broken "time" command that
8671            outputs a string not ending in newline.
8672            Don't use it. */
8673         cps->sendTime = 0;
8674     }
8675
8676     /*
8677      * If chess program startup fails, exit with an error message.
8678      * Attempts to recover here are futile. [HGM] Well, we try anyway
8679      */
8680     if ((StrStr(message, "unknown host") != NULL)
8681         || (StrStr(message, "No remote directory") != NULL)
8682         || (StrStr(message, "not found") != NULL)
8683         || (StrStr(message, "No such file") != NULL)
8684         || (StrStr(message, "can't alloc") != NULL)
8685         || (StrStr(message, "Permission denied") != NULL)) {
8686
8687         cps->maybeThinking = FALSE;
8688         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8689                 _(cps->which), cps->program, cps->host, message);
8690         RemoveInputSource(cps->isr);
8691         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8692             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8693             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8694         }
8695         return;
8696     }
8697
8698     /*
8699      * Look for hint output
8700      */
8701     if (sscanf(message, "Hint: %s", buf1) == 1) {
8702         if (cps == &first && hintRequested) {
8703             hintRequested = FALSE;
8704             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8705                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8706                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8707                                     PosFlags(forwardMostMove),
8708                                     fromY, fromX, toY, toX, promoChar, buf1);
8709                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8710                 DisplayInformation(buf2);
8711             } else {
8712                 /* Hint move could not be parsed!? */
8713               snprintf(buf2, sizeof(buf2),
8714                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8715                         buf1, _(cps->which));
8716                 DisplayError(buf2, 0);
8717             }
8718         } else {
8719           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8720         }
8721         return;
8722     }
8723
8724     /*
8725      * Ignore other messages if game is not in progress
8726      */
8727     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8728         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8729
8730     /*
8731      * look for win, lose, draw, or draw offer
8732      */
8733     if (strncmp(message, "1-0", 3) == 0) {
8734         char *p, *q, *r = "";
8735         p = strchr(message, '{');
8736         if (p) {
8737             q = strchr(p, '}');
8738             if (q) {
8739                 *q = NULLCHAR;
8740                 r = p + 1;
8741             }
8742         }
8743         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8744         return;
8745     } else if (strncmp(message, "0-1", 3) == 0) {
8746         char *p, *q, *r = "";
8747         p = strchr(message, '{');
8748         if (p) {
8749             q = strchr(p, '}');
8750             if (q) {
8751                 *q = NULLCHAR;
8752                 r = p + 1;
8753             }
8754         }
8755         /* Kludge for Arasan 4.1 bug */
8756         if (strcmp(r, "Black resigns") == 0) {
8757             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8758             return;
8759         }
8760         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8761         return;
8762     } else if (strncmp(message, "1/2", 3) == 0) {
8763         char *p, *q, *r = "";
8764         p = strchr(message, '{');
8765         if (p) {
8766             q = strchr(p, '}');
8767             if (q) {
8768                 *q = NULLCHAR;
8769                 r = p + 1;
8770             }
8771         }
8772
8773         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8774         return;
8775
8776     } else if (strncmp(message, "White resign", 12) == 0) {
8777         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8778         return;
8779     } else if (strncmp(message, "Black resign", 12) == 0) {
8780         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8781         return;
8782     } else if (strncmp(message, "White matches", 13) == 0 ||
8783                strncmp(message, "Black matches", 13) == 0   ) {
8784         /* [HGM] ignore GNUShogi noises */
8785         return;
8786     } else if (strncmp(message, "White", 5) == 0 &&
8787                message[5] != '(' &&
8788                StrStr(message, "Black") == NULL) {
8789         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8790         return;
8791     } else if (strncmp(message, "Black", 5) == 0 &&
8792                message[5] != '(') {
8793         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8794         return;
8795     } else if (strcmp(message, "resign") == 0 ||
8796                strcmp(message, "computer resigns") == 0) {
8797         switch (gameMode) {
8798           case MachinePlaysBlack:
8799           case IcsPlayingBlack:
8800             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8801             break;
8802           case MachinePlaysWhite:
8803           case IcsPlayingWhite:
8804             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8805             break;
8806           case TwoMachinesPlay:
8807             if (cps->twoMachinesColor[0] == 'w')
8808               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8809             else
8810               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8811             break;
8812           default:
8813             /* can't happen */
8814             break;
8815         }
8816         return;
8817     } else if (strncmp(message, "opponent mates", 14) == 0) {
8818         switch (gameMode) {
8819           case MachinePlaysBlack:
8820           case IcsPlayingBlack:
8821             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8822             break;
8823           case MachinePlaysWhite:
8824           case IcsPlayingWhite:
8825             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8826             break;
8827           case TwoMachinesPlay:
8828             if (cps->twoMachinesColor[0] == 'w')
8829               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8830             else
8831               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8832             break;
8833           default:
8834             /* can't happen */
8835             break;
8836         }
8837         return;
8838     } else if (strncmp(message, "computer mates", 14) == 0) {
8839         switch (gameMode) {
8840           case MachinePlaysBlack:
8841           case IcsPlayingBlack:
8842             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8843             break;
8844           case MachinePlaysWhite:
8845           case IcsPlayingWhite:
8846             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8847             break;
8848           case TwoMachinesPlay:
8849             if (cps->twoMachinesColor[0] == 'w')
8850               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8851             else
8852               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8853             break;
8854           default:
8855             /* can't happen */
8856             break;
8857         }
8858         return;
8859     } else if (strncmp(message, "checkmate", 9) == 0) {
8860         if (WhiteOnMove(forwardMostMove)) {
8861             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8862         } else {
8863             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8864         }
8865         return;
8866     } else if (strstr(message, "Draw") != NULL ||
8867                strstr(message, "game is a draw") != NULL) {
8868         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8869         return;
8870     } else if (strstr(message, "offer") != NULL &&
8871                strstr(message, "draw") != NULL) {
8872 #if ZIPPY
8873         if (appData.zippyPlay && first.initDone) {
8874             /* Relay offer to ICS */
8875             SendToICS(ics_prefix);
8876             SendToICS("draw\n");
8877         }
8878 #endif
8879         cps->offeredDraw = 2; /* valid until this engine moves twice */
8880         if (gameMode == TwoMachinesPlay) {
8881             if (cps->other->offeredDraw) {
8882                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8883             /* [HGM] in two-machine mode we delay relaying draw offer      */
8884             /* until after we also have move, to see if it is really claim */
8885             }
8886         } else if (gameMode == MachinePlaysWhite ||
8887                    gameMode == MachinePlaysBlack) {
8888           if (userOfferedDraw) {
8889             DisplayInformation(_("Machine accepts your draw offer"));
8890             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8891           } else {
8892             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8893           }
8894         }
8895     }
8896
8897
8898     /*
8899      * Look for thinking output
8900      */
8901     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8902           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8903                                 ) {
8904         int plylev, mvleft, mvtot, curscore, time;
8905         char mvname[MOVE_LEN];
8906         u64 nodes; // [DM]
8907         char plyext;
8908         int ignore = FALSE;
8909         int prefixHint = FALSE;
8910         mvname[0] = NULLCHAR;
8911
8912         switch (gameMode) {
8913           case MachinePlaysBlack:
8914           case IcsPlayingBlack:
8915             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8916             break;
8917           case MachinePlaysWhite:
8918           case IcsPlayingWhite:
8919             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8920             break;
8921           case AnalyzeMode:
8922           case AnalyzeFile:
8923             break;
8924           case IcsObserving: /* [DM] icsEngineAnalyze */
8925             if (!appData.icsEngineAnalyze) ignore = TRUE;
8926             break;
8927           case TwoMachinesPlay:
8928             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8929                 ignore = TRUE;
8930             }
8931             break;
8932           default:
8933             ignore = TRUE;
8934             break;
8935         }
8936
8937         if (!ignore) {
8938             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8939             buf1[0] = NULLCHAR;
8940             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8941                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8942
8943                 if (plyext != ' ' && plyext != '\t') {
8944                     time *= 100;
8945                 }
8946
8947                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8948                 if( cps->scoreIsAbsolute &&
8949                     ( gameMode == MachinePlaysBlack ||
8950                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8951                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8952                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8953                      !WhiteOnMove(currentMove)
8954                     ) )
8955                 {
8956                     curscore = -curscore;
8957                 }
8958
8959                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8960
8961                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8962                         char buf[MSG_SIZ];
8963                         FILE *f;
8964                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8965                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8966                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8967                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8968                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8969                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8970                                 fclose(f);
8971                         } else DisplayError(_("failed writing PV"), 0);
8972                 }
8973
8974                 tempStats.depth = plylev;
8975                 tempStats.nodes = nodes;
8976                 tempStats.time = time;
8977                 tempStats.score = curscore;
8978                 tempStats.got_only_move = 0;
8979
8980                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8981                         int ticklen;
8982
8983                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8984                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8985                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8986                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8987                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8988                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8989                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8990                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8991                 }
8992
8993                 /* Buffer overflow protection */
8994                 if (pv[0] != NULLCHAR) {
8995                     if (strlen(pv) >= sizeof(tempStats.movelist)
8996                         && appData.debugMode) {
8997                         fprintf(debugFP,
8998                                 "PV is too long; using the first %u bytes.\n",
8999                                 (unsigned) sizeof(tempStats.movelist) - 1);
9000                     }
9001
9002                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9003                 } else {
9004                     sprintf(tempStats.movelist, " no PV\n");
9005                 }
9006
9007                 if (tempStats.seen_stat) {
9008                     tempStats.ok_to_send = 1;
9009                 }
9010
9011                 if (strchr(tempStats.movelist, '(') != NULL) {
9012                     tempStats.line_is_book = 1;
9013                     tempStats.nr_moves = 0;
9014                     tempStats.moves_left = 0;
9015                 } else {
9016                     tempStats.line_is_book = 0;
9017                 }
9018
9019                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9020                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9021
9022                 SendProgramStatsToFrontend( cps, &tempStats );
9023
9024                 /*
9025                     [AS] Protect the thinkOutput buffer from overflow... this
9026                     is only useful if buf1 hasn't overflowed first!
9027                 */
9028                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9029                          plylev,
9030                          (gameMode == TwoMachinesPlay ?
9031                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9032                          ((double) curscore) / 100.0,
9033                          prefixHint ? lastHint : "",
9034                          prefixHint ? " " : "" );
9035
9036                 if( buf1[0] != NULLCHAR ) {
9037                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9038
9039                     if( strlen(pv) > max_len ) {
9040                         if( appData.debugMode) {
9041                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9042                         }
9043                         pv[max_len+1] = '\0';
9044                     }
9045
9046                     strcat( thinkOutput, pv);
9047                 }
9048
9049                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9050                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9051                     DisplayMove(currentMove - 1);
9052                 }
9053                 return;
9054
9055             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9056                 /* crafty (9.25+) says "(only move) <move>"
9057                  * if there is only 1 legal move
9058                  */
9059                 sscanf(p, "(only move) %s", buf1);
9060                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9061                 sprintf(programStats.movelist, "%s (only move)", buf1);
9062                 programStats.depth = 1;
9063                 programStats.nr_moves = 1;
9064                 programStats.moves_left = 1;
9065                 programStats.nodes = 1;
9066                 programStats.time = 1;
9067                 programStats.got_only_move = 1;
9068
9069                 /* Not really, but we also use this member to
9070                    mean "line isn't going to change" (Crafty
9071                    isn't searching, so stats won't change) */
9072                 programStats.line_is_book = 1;
9073
9074                 SendProgramStatsToFrontend( cps, &programStats );
9075
9076                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9077                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9078                     DisplayMove(currentMove - 1);
9079                 }
9080                 return;
9081             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9082                               &time, &nodes, &plylev, &mvleft,
9083                               &mvtot, mvname) >= 5) {
9084                 /* The stat01: line is from Crafty (9.29+) in response
9085                    to the "." command */
9086                 programStats.seen_stat = 1;
9087                 cps->maybeThinking = TRUE;
9088
9089                 if (programStats.got_only_move || !appData.periodicUpdates)
9090                   return;
9091
9092                 programStats.depth = plylev;
9093                 programStats.time = time;
9094                 programStats.nodes = nodes;
9095                 programStats.moves_left = mvleft;
9096                 programStats.nr_moves = mvtot;
9097                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9098                 programStats.ok_to_send = 1;
9099                 programStats.movelist[0] = '\0';
9100
9101                 SendProgramStatsToFrontend( cps, &programStats );
9102
9103                 return;
9104
9105             } else if (strncmp(message,"++",2) == 0) {
9106                 /* Crafty 9.29+ outputs this */
9107                 programStats.got_fail = 2;
9108                 return;
9109
9110             } else if (strncmp(message,"--",2) == 0) {
9111                 /* Crafty 9.29+ outputs this */
9112                 programStats.got_fail = 1;
9113                 return;
9114
9115             } else if (thinkOutput[0] != NULLCHAR &&
9116                        strncmp(message, "    ", 4) == 0) {
9117                 unsigned message_len;
9118
9119                 p = message;
9120                 while (*p && *p == ' ') p++;
9121
9122                 message_len = strlen( p );
9123
9124                 /* [AS] Avoid buffer overflow */
9125                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9126                     strcat(thinkOutput, " ");
9127                     strcat(thinkOutput, p);
9128                 }
9129
9130                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9131                     strcat(programStats.movelist, " ");
9132                     strcat(programStats.movelist, p);
9133                 }
9134
9135                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9136                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9137                     DisplayMove(currentMove - 1);
9138                 }
9139                 return;
9140             }
9141         }
9142         else {
9143             buf1[0] = NULLCHAR;
9144
9145             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9146                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9147             {
9148                 ChessProgramStats cpstats;
9149
9150                 if (plyext != ' ' && plyext != '\t') {
9151                     time *= 100;
9152                 }
9153
9154                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9155                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9156                     curscore = -curscore;
9157                 }
9158
9159                 cpstats.depth = plylev;
9160                 cpstats.nodes = nodes;
9161                 cpstats.time = time;
9162                 cpstats.score = curscore;
9163                 cpstats.got_only_move = 0;
9164                 cpstats.movelist[0] = '\0';
9165
9166                 if (buf1[0] != NULLCHAR) {
9167                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9168                 }
9169
9170                 cpstats.ok_to_send = 0;
9171                 cpstats.line_is_book = 0;
9172                 cpstats.nr_moves = 0;
9173                 cpstats.moves_left = 0;
9174
9175                 SendProgramStatsToFrontend( cps, &cpstats );
9176             }
9177         }
9178     }
9179 }
9180
9181
9182 /* Parse a game score from the character string "game", and
9183    record it as the history of the current game.  The game
9184    score is NOT assumed to start from the standard position.
9185    The display is not updated in any way.
9186    */
9187 void
9188 ParseGameHistory (char *game)
9189 {
9190     ChessMove moveType;
9191     int fromX, fromY, toX, toY, boardIndex;
9192     char promoChar;
9193     char *p, *q;
9194     char buf[MSG_SIZ];
9195
9196     if (appData.debugMode)
9197       fprintf(debugFP, "Parsing game history: %s\n", game);
9198
9199     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9200     gameInfo.site = StrSave(appData.icsHost);
9201     gameInfo.date = PGNDate();
9202     gameInfo.round = StrSave("-");
9203
9204     /* Parse out names of players */
9205     while (*game == ' ') game++;
9206     p = buf;
9207     while (*game != ' ') *p++ = *game++;
9208     *p = NULLCHAR;
9209     gameInfo.white = StrSave(buf);
9210     while (*game == ' ') game++;
9211     p = buf;
9212     while (*game != ' ' && *game != '\n') *p++ = *game++;
9213     *p = NULLCHAR;
9214     gameInfo.black = StrSave(buf);
9215
9216     /* Parse moves */
9217     boardIndex = blackPlaysFirst ? 1 : 0;
9218     yynewstr(game);
9219     for (;;) {
9220         yyboardindex = boardIndex;
9221         moveType = (ChessMove) Myylex();
9222         switch (moveType) {
9223           case IllegalMove:             /* maybe suicide chess, etc. */
9224   if (appData.debugMode) {
9225     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9226     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9227     setbuf(debugFP, NULL);
9228   }
9229           case WhitePromotion:
9230           case BlackPromotion:
9231           case WhiteNonPromotion:
9232           case BlackNonPromotion:
9233           case NormalMove:
9234           case WhiteCapturesEnPassant:
9235           case BlackCapturesEnPassant:
9236           case WhiteKingSideCastle:
9237           case WhiteQueenSideCastle:
9238           case BlackKingSideCastle:
9239           case BlackQueenSideCastle:
9240           case WhiteKingSideCastleWild:
9241           case WhiteQueenSideCastleWild:
9242           case BlackKingSideCastleWild:
9243           case BlackQueenSideCastleWild:
9244           /* PUSH Fabien */
9245           case WhiteHSideCastleFR:
9246           case WhiteASideCastleFR:
9247           case BlackHSideCastleFR:
9248           case BlackASideCastleFR:
9249           /* POP Fabien */
9250             fromX = currentMoveString[0] - AAA;
9251             fromY = currentMoveString[1] - ONE;
9252             toX = currentMoveString[2] - AAA;
9253             toY = currentMoveString[3] - ONE;
9254             promoChar = currentMoveString[4];
9255             break;
9256           case WhiteDrop:
9257           case BlackDrop:
9258             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9259             fromX = moveType == WhiteDrop ?
9260               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9261             (int) CharToPiece(ToLower(currentMoveString[0]));
9262             fromY = DROP_RANK;
9263             toX = currentMoveString[2] - AAA;
9264             toY = currentMoveString[3] - ONE;
9265             promoChar = NULLCHAR;
9266             break;
9267           case AmbiguousMove:
9268             /* bug? */
9269             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9270   if (appData.debugMode) {
9271     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9272     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9273     setbuf(debugFP, NULL);
9274   }
9275             DisplayError(buf, 0);
9276             return;
9277           case ImpossibleMove:
9278             /* bug? */
9279             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9280   if (appData.debugMode) {
9281     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9282     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9283     setbuf(debugFP, NULL);
9284   }
9285             DisplayError(buf, 0);
9286             return;
9287           case EndOfFile:
9288             if (boardIndex < backwardMostMove) {
9289                 /* Oops, gap.  How did that happen? */
9290                 DisplayError(_("Gap in move list"), 0);
9291                 return;
9292             }
9293             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9294             if (boardIndex > forwardMostMove) {
9295                 forwardMostMove = boardIndex;
9296             }
9297             return;
9298           case ElapsedTime:
9299             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9300                 strcat(parseList[boardIndex-1], " ");
9301                 strcat(parseList[boardIndex-1], yy_text);
9302             }
9303             continue;
9304           case Comment:
9305           case PGNTag:
9306           case NAG:
9307           default:
9308             /* ignore */
9309             continue;
9310           case WhiteWins:
9311           case BlackWins:
9312           case GameIsDrawn:
9313           case GameUnfinished:
9314             if (gameMode == IcsExamining) {
9315                 if (boardIndex < backwardMostMove) {
9316                     /* Oops, gap.  How did that happen? */
9317                     return;
9318                 }
9319                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9320                 return;
9321             }
9322             gameInfo.result = moveType;
9323             p = strchr(yy_text, '{');
9324             if (p == NULL) p = strchr(yy_text, '(');
9325             if (p == NULL) {
9326                 p = yy_text;
9327                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9328             } else {
9329                 q = strchr(p, *p == '{' ? '}' : ')');
9330                 if (q != NULL) *q = NULLCHAR;
9331                 p++;
9332             }
9333             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9334             gameInfo.resultDetails = StrSave(p);
9335             continue;
9336         }
9337         if (boardIndex >= forwardMostMove &&
9338             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9339             backwardMostMove = blackPlaysFirst ? 1 : 0;
9340             return;
9341         }
9342         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9343                                  fromY, fromX, toY, toX, promoChar,
9344                                  parseList[boardIndex]);
9345         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9346         /* currentMoveString is set as a side-effect of yylex */
9347         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9348         strcat(moveList[boardIndex], "\n");
9349         boardIndex++;
9350         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9351         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9352           case MT_NONE:
9353           case MT_STALEMATE:
9354           default:
9355             break;
9356           case MT_CHECK:
9357             if(gameInfo.variant != VariantShogi)
9358                 strcat(parseList[boardIndex - 1], "+");
9359             break;
9360           case MT_CHECKMATE:
9361           case MT_STAINMATE:
9362             strcat(parseList[boardIndex - 1], "#");
9363             break;
9364         }
9365     }
9366 }
9367
9368
9369 /* Apply a move to the given board  */
9370 void
9371 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9372 {
9373   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9374   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9375
9376     /* [HGM] compute & store e.p. status and castling rights for new position */
9377     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9378
9379       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9380       oldEP = (signed char)board[EP_STATUS];
9381       board[EP_STATUS] = EP_NONE;
9382
9383   if (fromY == DROP_RANK) {
9384         /* must be first */
9385         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9386             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9387             return;
9388         }
9389         piece = board[toY][toX] = (ChessSquare) fromX;
9390   } else {
9391       int i;
9392
9393       if( board[toY][toX] != EmptySquare )
9394            board[EP_STATUS] = EP_CAPTURE;
9395
9396       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9397            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9398                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9399       } else
9400       if( board[fromY][fromX] == WhitePawn ) {
9401            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9402                board[EP_STATUS] = EP_PAWN_MOVE;
9403            if( toY-fromY==2) {
9404                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9405                         gameInfo.variant != VariantBerolina || toX < fromX)
9406                       board[EP_STATUS] = toX | berolina;
9407                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9408                         gameInfo.variant != VariantBerolina || toX > fromX)
9409                       board[EP_STATUS] = toX;
9410            }
9411       } else
9412       if( board[fromY][fromX] == BlackPawn ) {
9413            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9414                board[EP_STATUS] = EP_PAWN_MOVE;
9415            if( toY-fromY== -2) {
9416                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9417                         gameInfo.variant != VariantBerolina || toX < fromX)
9418                       board[EP_STATUS] = toX | berolina;
9419                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9420                         gameInfo.variant != VariantBerolina || toX > fromX)
9421                       board[EP_STATUS] = toX;
9422            }
9423        }
9424
9425        for(i=0; i<nrCastlingRights; i++) {
9426            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9427               board[CASTLING][i] == toX   && castlingRank[i] == toY
9428              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9429        }
9430
9431        if(gameInfo.variant == VariantSChess) { // update virginity
9432            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9433            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9434            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9435            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9436        }
9437
9438      if (fromX == toX && fromY == toY) return;
9439
9440      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9441      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9442      if(gameInfo.variant == VariantKnightmate)
9443          king += (int) WhiteUnicorn - (int) WhiteKing;
9444
9445     /* Code added by Tord: */
9446     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9447     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9448         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9449       board[fromY][fromX] = EmptySquare;
9450       board[toY][toX] = EmptySquare;
9451       if((toX > fromX) != (piece == WhiteRook)) {
9452         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9453       } else {
9454         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9455       }
9456     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9457                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9458       board[fromY][fromX] = EmptySquare;
9459       board[toY][toX] = EmptySquare;
9460       if((toX > fromX) != (piece == BlackRook)) {
9461         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9462       } else {
9463         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9464       }
9465     /* End of code added by Tord */
9466
9467     } else if (board[fromY][fromX] == king
9468         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9469         && toY == fromY && toX > fromX+1) {
9470         board[fromY][fromX] = EmptySquare;
9471         board[toY][toX] = king;
9472         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9473         board[fromY][BOARD_RGHT-1] = EmptySquare;
9474     } else if (board[fromY][fromX] == king
9475         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9476                && toY == fromY && toX < fromX-1) {
9477         board[fromY][fromX] = EmptySquare;
9478         board[toY][toX] = king;
9479         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9480         board[fromY][BOARD_LEFT] = EmptySquare;
9481     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9482                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9483                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9484                ) {
9485         /* white pawn promotion */
9486         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9487         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9488             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9489         board[fromY][fromX] = EmptySquare;
9490     } else if ((fromY >= BOARD_HEIGHT>>1)
9491                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9492                && (toX != fromX)
9493                && gameInfo.variant != VariantXiangqi
9494                && gameInfo.variant != VariantBerolina
9495                && (board[fromY][fromX] == WhitePawn)
9496                && (board[toY][toX] == EmptySquare)) {
9497         board[fromY][fromX] = EmptySquare;
9498         board[toY][toX] = WhitePawn;
9499         captured = board[toY - 1][toX];
9500         board[toY - 1][toX] = EmptySquare;
9501     } else if ((fromY == BOARD_HEIGHT-4)
9502                && (toX == fromX)
9503                && gameInfo.variant == VariantBerolina
9504                && (board[fromY][fromX] == WhitePawn)
9505                && (board[toY][toX] == EmptySquare)) {
9506         board[fromY][fromX] = EmptySquare;
9507         board[toY][toX] = WhitePawn;
9508         if(oldEP & EP_BEROLIN_A) {
9509                 captured = board[fromY][fromX-1];
9510                 board[fromY][fromX-1] = EmptySquare;
9511         }else{  captured = board[fromY][fromX+1];
9512                 board[fromY][fromX+1] = EmptySquare;
9513         }
9514     } else if (board[fromY][fromX] == king
9515         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9516                && toY == fromY && toX > fromX+1) {
9517         board[fromY][fromX] = EmptySquare;
9518         board[toY][toX] = king;
9519         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9520         board[fromY][BOARD_RGHT-1] = EmptySquare;
9521     } else if (board[fromY][fromX] == king
9522         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9523                && toY == fromY && toX < fromX-1) {
9524         board[fromY][fromX] = EmptySquare;
9525         board[toY][toX] = king;
9526         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9527         board[fromY][BOARD_LEFT] = EmptySquare;
9528     } else if (fromY == 7 && fromX == 3
9529                && board[fromY][fromX] == BlackKing
9530                && toY == 7 && toX == 5) {
9531         board[fromY][fromX] = EmptySquare;
9532         board[toY][toX] = BlackKing;
9533         board[fromY][7] = EmptySquare;
9534         board[toY][4] = BlackRook;
9535     } else if (fromY == 7 && fromX == 3
9536                && board[fromY][fromX] == BlackKing
9537                && toY == 7 && toX == 1) {
9538         board[fromY][fromX] = EmptySquare;
9539         board[toY][toX] = BlackKing;
9540         board[fromY][0] = EmptySquare;
9541         board[toY][2] = BlackRook;
9542     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9543                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9544                && toY < promoRank && promoChar
9545                ) {
9546         /* black pawn promotion */
9547         board[toY][toX] = CharToPiece(ToLower(promoChar));
9548         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9549             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9550         board[fromY][fromX] = EmptySquare;
9551     } else if ((fromY < BOARD_HEIGHT>>1)
9552                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9553                && (toX != fromX)
9554                && gameInfo.variant != VariantXiangqi
9555                && gameInfo.variant != VariantBerolina
9556                && (board[fromY][fromX] == BlackPawn)
9557                && (board[toY][toX] == EmptySquare)) {
9558         board[fromY][fromX] = EmptySquare;
9559         board[toY][toX] = BlackPawn;
9560         captured = board[toY + 1][toX];
9561         board[toY + 1][toX] = EmptySquare;
9562     } else if ((fromY == 3)
9563                && (toX == fromX)
9564                && gameInfo.variant == VariantBerolina
9565                && (board[fromY][fromX] == BlackPawn)
9566                && (board[toY][toX] == EmptySquare)) {
9567         board[fromY][fromX] = EmptySquare;
9568         board[toY][toX] = BlackPawn;
9569         if(oldEP & EP_BEROLIN_A) {
9570                 captured = board[fromY][fromX-1];
9571                 board[fromY][fromX-1] = EmptySquare;
9572         }else{  captured = board[fromY][fromX+1];
9573                 board[fromY][fromX+1] = EmptySquare;
9574         }
9575     } else {
9576         board[toY][toX] = board[fromY][fromX];
9577         board[fromY][fromX] = EmptySquare;
9578     }
9579   }
9580
9581     if (gameInfo.holdingsWidth != 0) {
9582
9583       /* !!A lot more code needs to be written to support holdings  */
9584       /* [HGM] OK, so I have written it. Holdings are stored in the */
9585       /* penultimate board files, so they are automaticlly stored   */
9586       /* in the game history.                                       */
9587       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9588                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9589         /* Delete from holdings, by decreasing count */
9590         /* and erasing image if necessary            */
9591         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9592         if(p < (int) BlackPawn) { /* white drop */
9593              p -= (int)WhitePawn;
9594                  p = PieceToNumber((ChessSquare)p);
9595              if(p >= gameInfo.holdingsSize) p = 0;
9596              if(--board[p][BOARD_WIDTH-2] <= 0)
9597                   board[p][BOARD_WIDTH-1] = EmptySquare;
9598              if((int)board[p][BOARD_WIDTH-2] < 0)
9599                         board[p][BOARD_WIDTH-2] = 0;
9600         } else {                  /* black drop */
9601              p -= (int)BlackPawn;
9602                  p = PieceToNumber((ChessSquare)p);
9603              if(p >= gameInfo.holdingsSize) p = 0;
9604              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9605                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9606              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9607                         board[BOARD_HEIGHT-1-p][1] = 0;
9608         }
9609       }
9610       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9611           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9612         /* [HGM] holdings: Add to holdings, if holdings exist */
9613         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9614                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9615                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9616         }
9617         p = (int) captured;
9618         if (p >= (int) BlackPawn) {
9619           p -= (int)BlackPawn;
9620           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9621                   /* in Shogi restore piece to its original  first */
9622                   captured = (ChessSquare) (DEMOTED captured);
9623                   p = DEMOTED p;
9624           }
9625           p = PieceToNumber((ChessSquare)p);
9626           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9627           board[p][BOARD_WIDTH-2]++;
9628           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9629         } else {
9630           p -= (int)WhitePawn;
9631           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9632                   captured = (ChessSquare) (DEMOTED captured);
9633                   p = DEMOTED p;
9634           }
9635           p = PieceToNumber((ChessSquare)p);
9636           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9637           board[BOARD_HEIGHT-1-p][1]++;
9638           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9639         }
9640       }
9641     } else if (gameInfo.variant == VariantAtomic) {
9642       if (captured != EmptySquare) {
9643         int y, x;
9644         for (y = toY-1; y <= toY+1; y++) {
9645           for (x = toX-1; x <= toX+1; x++) {
9646             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9647                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9648               board[y][x] = EmptySquare;
9649             }
9650           }
9651         }
9652         board[toY][toX] = EmptySquare;
9653       }
9654     }
9655     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9656         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9657     } else
9658     if(promoChar == '+') {
9659         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9660         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9661     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9662         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9663         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9664            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9665         board[toY][toX] = newPiece;
9666     }
9667     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9668                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9669         // [HGM] superchess: take promotion piece out of holdings
9670         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9671         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9672             if(!--board[k][BOARD_WIDTH-2])
9673                 board[k][BOARD_WIDTH-1] = EmptySquare;
9674         } else {
9675             if(!--board[BOARD_HEIGHT-1-k][1])
9676                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9677         }
9678     }
9679
9680 }
9681
9682 /* Updates forwardMostMove */
9683 void
9684 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9685 {
9686 //    forwardMostMove++; // [HGM] bare: moved downstream
9687
9688     (void) CoordsToAlgebraic(boards[forwardMostMove],
9689                              PosFlags(forwardMostMove),
9690                              fromY, fromX, toY, toX, promoChar,
9691                              parseList[forwardMostMove]);
9692
9693     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9694         int timeLeft; static int lastLoadFlag=0; int king, piece;
9695         piece = boards[forwardMostMove][fromY][fromX];
9696         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9697         if(gameInfo.variant == VariantKnightmate)
9698             king += (int) WhiteUnicorn - (int) WhiteKing;
9699         if(forwardMostMove == 0) {
9700             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9701                 fprintf(serverMoves, "%s;", UserName());
9702             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9703                 fprintf(serverMoves, "%s;", second.tidy);
9704             fprintf(serverMoves, "%s;", first.tidy);
9705             if(gameMode == MachinePlaysWhite)
9706                 fprintf(serverMoves, "%s;", UserName());
9707             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9708                 fprintf(serverMoves, "%s;", second.tidy);
9709         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9710         lastLoadFlag = loadFlag;
9711         // print base move
9712         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9713         // print castling suffix
9714         if( toY == fromY && piece == king ) {
9715             if(toX-fromX > 1)
9716                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9717             if(fromX-toX >1)
9718                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9719         }
9720         // e.p. suffix
9721         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9722              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9723              boards[forwardMostMove][toY][toX] == EmptySquare
9724              && fromX != toX && fromY != toY)
9725                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9726         // promotion suffix
9727         if(promoChar != NULLCHAR) {
9728             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9729                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9730                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9731             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9732         }
9733         if(!loadFlag) {
9734                 char buf[MOVE_LEN*2], *p; int len;
9735             fprintf(serverMoves, "/%d/%d",
9736                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9737             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9738             else                      timeLeft = blackTimeRemaining/1000;
9739             fprintf(serverMoves, "/%d", timeLeft);
9740                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9741                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9742                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9743                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9744             fprintf(serverMoves, "/%s", buf);
9745         }
9746         fflush(serverMoves);
9747     }
9748
9749     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9750         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9751       return;
9752     }
9753     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9754     if (commentList[forwardMostMove+1] != NULL) {
9755         free(commentList[forwardMostMove+1]);
9756         commentList[forwardMostMove+1] = NULL;
9757     }
9758     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9759     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9760     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9761     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9762     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9763     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9764     adjustedClock = FALSE;
9765     gameInfo.result = GameUnfinished;
9766     if (gameInfo.resultDetails != NULL) {
9767         free(gameInfo.resultDetails);
9768         gameInfo.resultDetails = NULL;
9769     }
9770     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9771                               moveList[forwardMostMove - 1]);
9772     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9773       case MT_NONE:
9774       case MT_STALEMATE:
9775       default:
9776         break;
9777       case MT_CHECK:
9778         if(gameInfo.variant != VariantShogi)
9779             strcat(parseList[forwardMostMove - 1], "+");
9780         break;
9781       case MT_CHECKMATE:
9782       case MT_STAINMATE:
9783         strcat(parseList[forwardMostMove - 1], "#");
9784         break;
9785     }
9786
9787 }
9788
9789 /* Updates currentMove if not pausing */
9790 void
9791 ShowMove (int fromX, int fromY, int toX, int toY)
9792 {
9793     int instant = (gameMode == PlayFromGameFile) ?
9794         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9795     if(appData.noGUI) return;
9796     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9797         if (!instant) {
9798             if (forwardMostMove == currentMove + 1) {
9799                 AnimateMove(boards[forwardMostMove - 1],
9800                             fromX, fromY, toX, toY);
9801             }
9802         }
9803         currentMove = forwardMostMove;
9804     }
9805
9806     if (instant) return;
9807
9808     DisplayMove(currentMove - 1);
9809     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9810             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9811                 SetHighlights(fromX, fromY, toX, toY);
9812             }
9813     }
9814     DrawPosition(FALSE, boards[currentMove]);
9815     DisplayBothClocks();
9816     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9817 }
9818
9819 void
9820 SendEgtPath (ChessProgramState *cps)
9821 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9822         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9823
9824         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9825
9826         while(*p) {
9827             char c, *q = name+1, *r, *s;
9828
9829             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9830             while(*p && *p != ',') *q++ = *p++;
9831             *q++ = ':'; *q = 0;
9832             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9833                 strcmp(name, ",nalimov:") == 0 ) {
9834                 // take nalimov path from the menu-changeable option first, if it is defined
9835               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9836                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9837             } else
9838             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9839                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9840                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9841                 s = r = StrStr(s, ":") + 1; // beginning of path info
9842                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9843                 c = *r; *r = 0;             // temporarily null-terminate path info
9844                     *--q = 0;               // strip of trailig ':' from name
9845                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9846                 *r = c;
9847                 SendToProgram(buf,cps);     // send egtbpath command for this format
9848             }
9849             if(*p == ',') p++; // read away comma to position for next format name
9850         }
9851 }
9852
9853 void
9854 InitChessProgram (ChessProgramState *cps, int setup)
9855 /* setup needed to setup FRC opening position */
9856 {
9857     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9858     if (appData.noChessProgram) return;
9859     hintRequested = FALSE;
9860     bookRequested = FALSE;
9861
9862     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9863     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9864     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9865     if(cps->memSize) { /* [HGM] memory */
9866       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9867         SendToProgram(buf, cps);
9868     }
9869     SendEgtPath(cps); /* [HGM] EGT */
9870     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9871       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9872         SendToProgram(buf, cps);
9873     }
9874
9875     SendToProgram(cps->initString, cps);
9876     if (gameInfo.variant != VariantNormal &&
9877         gameInfo.variant != VariantLoadable
9878         /* [HGM] also send variant if board size non-standard */
9879         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9880                                             ) {
9881       char *v = VariantName(gameInfo.variant);
9882       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9883         /* [HGM] in protocol 1 we have to assume all variants valid */
9884         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9885         DisplayFatalError(buf, 0, 1);
9886         return;
9887       }
9888
9889       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9890       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9891       if( gameInfo.variant == VariantXiangqi )
9892            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9893       if( gameInfo.variant == VariantShogi )
9894            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9895       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9896            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9897       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9898           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9899            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9900       if( gameInfo.variant == VariantCourier )
9901            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9902       if( gameInfo.variant == VariantSuper )
9903            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9904       if( gameInfo.variant == VariantGreat )
9905            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9906       if( gameInfo.variant == VariantSChess )
9907            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9908       if( gameInfo.variant == VariantGrand )
9909            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9910
9911       if(overruled) {
9912         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9913                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9914            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9915            if(StrStr(cps->variants, b) == NULL) {
9916                // specific sized variant not known, check if general sizing allowed
9917                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9918                    if(StrStr(cps->variants, "boardsize") == NULL) {
9919                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9920                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9921                        DisplayFatalError(buf, 0, 1);
9922                        return;
9923                    }
9924                    /* [HGM] here we really should compare with the maximum supported board size */
9925                }
9926            }
9927       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9928       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9929       SendToProgram(buf, cps);
9930     }
9931     currentlyInitializedVariant = gameInfo.variant;
9932
9933     /* [HGM] send opening position in FRC to first engine */
9934     if(setup) {
9935           SendToProgram("force\n", cps);
9936           SendBoard(cps, 0);
9937           /* engine is now in force mode! Set flag to wake it up after first move. */
9938           setboardSpoiledMachineBlack = 1;
9939     }
9940
9941     if (cps->sendICS) {
9942       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9943       SendToProgram(buf, cps);
9944     }
9945     cps->maybeThinking = FALSE;
9946     cps->offeredDraw = 0;
9947     if (!appData.icsActive) {
9948         SendTimeControl(cps, movesPerSession, timeControl,
9949                         timeIncrement, appData.searchDepth,
9950                         searchTime);
9951     }
9952     if (appData.showThinking
9953         // [HGM] thinking: four options require thinking output to be sent
9954         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9955                                 ) {
9956         SendToProgram("post\n", cps);
9957     }
9958     SendToProgram("hard\n", cps);
9959     if (!appData.ponderNextMove) {
9960         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9961            it without being sure what state we are in first.  "hard"
9962            is not a toggle, so that one is OK.
9963          */
9964         SendToProgram("easy\n", cps);
9965     }
9966     if (cps->usePing) {
9967       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9968       SendToProgram(buf, cps);
9969     }
9970     cps->initDone = TRUE;
9971     ClearEngineOutputPane(cps == &second);
9972 }
9973
9974
9975 void
9976 ResendOptions (ChessProgramState *cps)
9977 { // send the stored value of the options
9978   int i;
9979   char buf[MSG_SIZ];
9980   Option *opt = cps->option;
9981   for(i=0; i<cps->nrOptions; i++, opt++) {
9982       switch(opt->type) {
9983         case Spin:
9984         case Slider:
9985         case CheckBox:
9986             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9987           break;
9988         case ComboBox:
9989           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9990           break;
9991         default:
9992             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9993           break;
9994         case Button:
9995         case SaveButton:
9996           continue;
9997       }
9998       SendToProgram(buf, cps);
9999   }
10000 }
10001
10002 void
10003 StartChessProgram (ChessProgramState *cps)
10004 {
10005     char buf[MSG_SIZ];
10006     int err;
10007
10008     if (appData.noChessProgram) return;
10009     cps->initDone = FALSE;
10010
10011     if (strcmp(cps->host, "localhost") == 0) {
10012         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10013     } else if (*appData.remoteShell == NULLCHAR) {
10014         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10015     } else {
10016         if (*appData.remoteUser == NULLCHAR) {
10017           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10018                     cps->program);
10019         } else {
10020           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10021                     cps->host, appData.remoteUser, cps->program);
10022         }
10023         err = StartChildProcess(buf, "", &cps->pr);
10024     }
10025
10026     if (err != 0) {
10027       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10028         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10029         if(cps != &first) return;
10030         appData.noChessProgram = TRUE;
10031         ThawUI();
10032         SetNCPMode();
10033 //      DisplayFatalError(buf, err, 1);
10034 //      cps->pr = NoProc;
10035 //      cps->isr = NULL;
10036         return;
10037     }
10038
10039     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10040     if (cps->protocolVersion > 1) {
10041       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10042       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10043         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10044         cps->comboCnt = 0;  //                and values of combo boxes
10045       }
10046       SendToProgram(buf, cps);
10047       if(cps->reload) ResendOptions(cps);
10048     } else {
10049       SendToProgram("xboard\n", cps);
10050     }
10051 }
10052
10053 void
10054 TwoMachinesEventIfReady P((void))
10055 {
10056   static int curMess = 0;
10057   if (first.lastPing != first.lastPong) {
10058     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10059     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10060     return;
10061   }
10062   if (second.lastPing != second.lastPong) {
10063     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10064     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10065     return;
10066   }
10067   DisplayMessage("", ""); curMess = 0;
10068   TwoMachinesEvent();
10069 }
10070
10071 char *
10072 MakeName (char *template)
10073 {
10074     time_t clock;
10075     struct tm *tm;
10076     static char buf[MSG_SIZ];
10077     char *p = buf;
10078     int i;
10079
10080     clock = time((time_t *)NULL);
10081     tm = localtime(&clock);
10082
10083     while(*p++ = *template++) if(p[-1] == '%') {
10084         switch(*template++) {
10085           case 0:   *p = 0; return buf;
10086           case 'Y': i = tm->tm_year+1900; break;
10087           case 'y': i = tm->tm_year-100; break;
10088           case 'M': i = tm->tm_mon+1; break;
10089           case 'd': i = tm->tm_mday; break;
10090           case 'h': i = tm->tm_hour; break;
10091           case 'm': i = tm->tm_min; break;
10092           case 's': i = tm->tm_sec; break;
10093           default:  i = 0;
10094         }
10095         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10096     }
10097     return buf;
10098 }
10099
10100 int
10101 CountPlayers (char *p)
10102 {
10103     int n = 0;
10104     while(p = strchr(p, '\n')) p++, n++; // count participants
10105     return n;
10106 }
10107
10108 FILE *
10109 WriteTourneyFile (char *results, FILE *f)
10110 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10111     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10112     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10113         // create a file with tournament description
10114         fprintf(f, "-participants {%s}\n", appData.participants);
10115         fprintf(f, "-seedBase %d\n", appData.seedBase);
10116         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10117         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10118         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10119         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10120         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10121         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10122         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10123         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10124         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10125         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10126         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10127         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10128         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10129         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10130         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10131         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10132         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10133         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10134         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10135         fprintf(f, "-smpCores %d\n", appData.smpCores);
10136         if(searchTime > 0)
10137                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10138         else {
10139                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10140                 fprintf(f, "-tc %s\n", appData.timeControl);
10141                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10142         }
10143         fprintf(f, "-results \"%s\"\n", results);
10144     }
10145     return f;
10146 }
10147
10148 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10149
10150 void
10151 Substitute (char *participants, int expunge)
10152 {
10153     int i, changed, changes=0, nPlayers=0;
10154     char *p, *q, *r, buf[MSG_SIZ];
10155     if(participants == NULL) return;
10156     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10157     r = p = participants; q = appData.participants;
10158     while(*p && *p == *q) {
10159         if(*p == '\n') r = p+1, nPlayers++;
10160         p++; q++;
10161     }
10162     if(*p) { // difference
10163         while(*p && *p++ != '\n');
10164         while(*q && *q++ != '\n');
10165       changed = nPlayers;
10166         changes = 1 + (strcmp(p, q) != 0);
10167     }
10168     if(changes == 1) { // a single engine mnemonic was changed
10169         q = r; while(*q) nPlayers += (*q++ == '\n');
10170         p = buf; while(*r && (*p = *r++) != '\n') p++;
10171         *p = NULLCHAR;
10172         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10173         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10174         if(mnemonic[i]) { // The substitute is valid
10175             FILE *f;
10176             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10177                 flock(fileno(f), LOCK_EX);
10178                 ParseArgsFromFile(f);
10179                 fseek(f, 0, SEEK_SET);
10180                 FREE(appData.participants); appData.participants = participants;
10181                 if(expunge) { // erase results of replaced engine
10182                     int len = strlen(appData.results), w, b, dummy;
10183                     for(i=0; i<len; i++) {
10184                         Pairing(i, nPlayers, &w, &b, &dummy);
10185                         if((w == changed || b == changed) && appData.results[i] == '*') {
10186                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10187                             fclose(f);
10188                             return;
10189                         }
10190                     }
10191                     for(i=0; i<len; i++) {
10192                         Pairing(i, nPlayers, &w, &b, &dummy);
10193                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10194                     }
10195                 }
10196                 WriteTourneyFile(appData.results, f);
10197                 fclose(f); // release lock
10198                 return;
10199             }
10200         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10201     }
10202     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10203     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10204     free(participants);
10205     return;
10206 }
10207
10208 int
10209 CheckPlayers (char *participants)
10210 {
10211         int i;
10212         char buf[MSG_SIZ], *p;
10213         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10214         while(p = strchr(participants, '\n')) {
10215             *p = NULLCHAR;
10216             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10217             if(!mnemonic[i]) {
10218                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10219                 *p = '\n';
10220                 DisplayError(buf, 0);
10221                 return 1;
10222             }
10223             *p = '\n';
10224             participants = p + 1;
10225         }
10226         return 0;
10227 }
10228
10229 int
10230 CreateTourney (char *name)
10231 {
10232         FILE *f;
10233         if(matchMode && strcmp(name, appData.tourneyFile)) {
10234              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10235         }
10236         if(name[0] == NULLCHAR) {
10237             if(appData.participants[0])
10238                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10239             return 0;
10240         }
10241         f = fopen(name, "r");
10242         if(f) { // file exists
10243             ASSIGN(appData.tourneyFile, name);
10244             ParseArgsFromFile(f); // parse it
10245         } else {
10246             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10247             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10248                 DisplayError(_("Not enough participants"), 0);
10249                 return 0;
10250             }
10251             if(CheckPlayers(appData.participants)) return 0;
10252             ASSIGN(appData.tourneyFile, name);
10253             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10254             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10255         }
10256         fclose(f);
10257         appData.noChessProgram = FALSE;
10258         appData.clockMode = TRUE;
10259         SetGNUMode();
10260         return 1;
10261 }
10262
10263 int
10264 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10265 {
10266     char buf[MSG_SIZ], *p, *q;
10267     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10268     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10269     skip = !all && group[0]; // if group requested, we start in skip mode
10270     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10271         p = names; q = buf; header = 0;
10272         while(*p && *p != '\n') *q++ = *p++;
10273         *q = 0;
10274         if(*p == '\n') p++;
10275         if(buf[0] == '#') {
10276             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10277             depth++; // we must be entering a new group
10278             if(all) continue; // suppress printing group headers when complete list requested
10279             header = 1;
10280             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10281         }
10282         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10283         if(engineList[i]) free(engineList[i]);
10284         engineList[i] = strdup(buf);
10285         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10286         if(engineMnemonic[i]) free(engineMnemonic[i]);
10287         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10288             strcat(buf, " (");
10289             sscanf(q + 8, "%s", buf + strlen(buf));
10290             strcat(buf, ")");
10291         }
10292         engineMnemonic[i] = strdup(buf);
10293         i++;
10294     }
10295     engineList[i] = engineMnemonic[i] = NULL;
10296     return i;
10297 }
10298
10299 // following implemented as macro to avoid type limitations
10300 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10301
10302 void
10303 SwapEngines (int n)
10304 {   // swap settings for first engine and other engine (so far only some selected options)
10305     int h;
10306     char *p;
10307     if(n == 0) return;
10308     SWAP(directory, p)
10309     SWAP(chessProgram, p)
10310     SWAP(isUCI, h)
10311     SWAP(hasOwnBookUCI, h)
10312     SWAP(protocolVersion, h)
10313     SWAP(reuse, h)
10314     SWAP(scoreIsAbsolute, h)
10315     SWAP(timeOdds, h)
10316     SWAP(logo, p)
10317     SWAP(pgnName, p)
10318     SWAP(pvSAN, h)
10319     SWAP(engOptions, p)
10320     SWAP(engInitString, p)
10321     SWAP(computerString, p)
10322     SWAP(features, p)
10323     SWAP(fenOverride, p)
10324     SWAP(NPS, h)
10325     SWAP(accumulateTC, h)
10326     SWAP(host, p)
10327 }
10328
10329 int
10330 GetEngineLine (char *s, int n)
10331 {
10332     int i;
10333     char buf[MSG_SIZ];
10334     extern char *icsNames;
10335     if(!s || !*s) return 0;
10336     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10337     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10338     if(!mnemonic[i]) return 0;
10339     if(n == 11) return 1; // just testing if there was a match
10340     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10341     if(n == 1) SwapEngines(n);
10342     ParseArgsFromString(buf);
10343     if(n == 1) SwapEngines(n);
10344     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10345         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10346         ParseArgsFromString(buf);
10347     }
10348     return 1;
10349 }
10350
10351 int
10352 SetPlayer (int player, char *p)
10353 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10354     int i;
10355     char buf[MSG_SIZ], *engineName;
10356     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10357     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10358     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10359     if(mnemonic[i]) {
10360         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10361         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10362         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10363         ParseArgsFromString(buf);
10364     } else { // no engine with this nickname is installed!
10365         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10366         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10367         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10368         ModeHighlight();
10369         DisplayError(buf, 0);
10370         return 0;
10371     }
10372     free(engineName);
10373     return i;
10374 }
10375
10376 char *recentEngines;
10377
10378 void
10379 RecentEngineEvent (int nr)
10380 {
10381     int n;
10382 //    SwapEngines(1); // bump first to second
10383 //    ReplaceEngine(&second, 1); // and load it there
10384     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10385     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10386     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10387         ReplaceEngine(&first, 0);
10388         FloatToFront(&appData.recentEngineList, command[n]);
10389     }
10390 }
10391
10392 int
10393 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10394 {   // determine players from game number
10395     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10396
10397     if(appData.tourneyType == 0) {
10398         roundsPerCycle = (nPlayers - 1) | 1;
10399         pairingsPerRound = nPlayers / 2;
10400     } else if(appData.tourneyType > 0) {
10401         roundsPerCycle = nPlayers - appData.tourneyType;
10402         pairingsPerRound = appData.tourneyType;
10403     }
10404     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10405     gamesPerCycle = gamesPerRound * roundsPerCycle;
10406     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10407     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10408     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10409     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10410     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10411     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10412
10413     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10414     if(appData.roundSync) *syncInterval = gamesPerRound;
10415
10416     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10417
10418     if(appData.tourneyType == 0) {
10419         if(curPairing == (nPlayers-1)/2 ) {
10420             *whitePlayer = curRound;
10421             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10422         } else {
10423             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10424             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10425             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10426             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10427         }
10428     } else if(appData.tourneyType > 1) {
10429         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10430         *whitePlayer = curRound + appData.tourneyType;
10431     } else if(appData.tourneyType > 0) {
10432         *whitePlayer = curPairing;
10433         *blackPlayer = curRound + appData.tourneyType;
10434     }
10435
10436     // take care of white/black alternation per round.
10437     // For cycles and games this is already taken care of by default, derived from matchGame!
10438     return curRound & 1;
10439 }
10440
10441 int
10442 NextTourneyGame (int nr, int *swapColors)
10443 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10444     char *p, *q;
10445     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10446     FILE *tf;
10447     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10448     tf = fopen(appData.tourneyFile, "r");
10449     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10450     ParseArgsFromFile(tf); fclose(tf);
10451     InitTimeControls(); // TC might be altered from tourney file
10452
10453     nPlayers = CountPlayers(appData.participants); // count participants
10454     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10455     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10456
10457     if(syncInterval) {
10458         p = q = appData.results;
10459         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10460         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10461             DisplayMessage(_("Waiting for other game(s)"),"");
10462             waitingForGame = TRUE;
10463             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10464             return 0;
10465         }
10466         waitingForGame = FALSE;
10467     }
10468
10469     if(appData.tourneyType < 0) {
10470         if(nr>=0 && !pairingReceived) {
10471             char buf[1<<16];
10472             if(pairing.pr == NoProc) {
10473                 if(!appData.pairingEngine[0]) {
10474                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10475                     return 0;
10476                 }
10477                 StartChessProgram(&pairing); // starts the pairing engine
10478             }
10479             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10480             SendToProgram(buf, &pairing);
10481             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10482             SendToProgram(buf, &pairing);
10483             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10484         }
10485         pairingReceived = 0;                              // ... so we continue here
10486         *swapColors = 0;
10487         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10488         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10489         matchGame = 1; roundNr = nr / syncInterval + 1;
10490     }
10491
10492     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10493
10494     // redefine engines, engine dir, etc.
10495     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10496     if(first.pr == NoProc) {
10497       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10498       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10499     }
10500     if(second.pr == NoProc) {
10501       SwapEngines(1);
10502       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10503       SwapEngines(1);         // and make that valid for second engine by swapping
10504       InitEngine(&second, 1);
10505     }
10506     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10507     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10508     return OK;
10509 }
10510
10511 void
10512 NextMatchGame ()
10513 {   // performs game initialization that does not invoke engines, and then tries to start the game
10514     int res, firstWhite, swapColors = 0;
10515     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10516     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
10517         char buf[MSG_SIZ];
10518         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10519         if(strcmp(buf, currentDebugFile)) { // name has changed
10520             FILE *f = fopen(buf, "w");
10521             if(f) { // if opening the new file failed, just keep using the old one
10522                 ASSIGN(currentDebugFile, buf);
10523                 fclose(debugFP);
10524                 debugFP = f;
10525             }
10526             if(appData.serverFileName) {
10527                 if(serverFP) fclose(serverFP);
10528                 serverFP = fopen(appData.serverFileName, "w");
10529                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10530                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10531             }
10532         }
10533     }
10534     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10535     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10536     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10537     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10538     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10539     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10540     Reset(FALSE, first.pr != NoProc);
10541     res = LoadGameOrPosition(matchGame); // setup game
10542     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10543     if(!res) return; // abort when bad game/pos file
10544     TwoMachinesEvent();
10545 }
10546
10547 void
10548 UserAdjudicationEvent (int result)
10549 {
10550     ChessMove gameResult = GameIsDrawn;
10551
10552     if( result > 0 ) {
10553         gameResult = WhiteWins;
10554     }
10555     else if( result < 0 ) {
10556         gameResult = BlackWins;
10557     }
10558
10559     if( gameMode == TwoMachinesPlay ) {
10560         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10561     }
10562 }
10563
10564
10565 // [HGM] save: calculate checksum of game to make games easily identifiable
10566 int
10567 StringCheckSum (char *s)
10568 {
10569         int i = 0;
10570         if(s==NULL) return 0;
10571         while(*s) i = i*259 + *s++;
10572         return i;
10573 }
10574
10575 int
10576 GameCheckSum ()
10577 {
10578         int i, sum=0;
10579         for(i=backwardMostMove; i<forwardMostMove; i++) {
10580                 sum += pvInfoList[i].depth;
10581                 sum += StringCheckSum(parseList[i]);
10582                 sum += StringCheckSum(commentList[i]);
10583                 sum *= 261;
10584         }
10585         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10586         return sum + StringCheckSum(commentList[i]);
10587 } // end of save patch
10588
10589 void
10590 GameEnds (ChessMove result, char *resultDetails, int whosays)
10591 {
10592     GameMode nextGameMode;
10593     int isIcsGame;
10594     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10595
10596     if(endingGame) return; /* [HGM] crash: forbid recursion */
10597     endingGame = 1;
10598     if(twoBoards) { // [HGM] dual: switch back to one board
10599         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10600         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10601     }
10602     if (appData.debugMode) {
10603       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10604               result, resultDetails ? resultDetails : "(null)", whosays);
10605     }
10606
10607     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10608
10609     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10610
10611     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10612         /* If we are playing on ICS, the server decides when the
10613            game is over, but the engine can offer to draw, claim
10614            a draw, or resign.
10615          */
10616 #if ZIPPY
10617         if (appData.zippyPlay && first.initDone) {
10618             if (result == GameIsDrawn) {
10619                 /* In case draw still needs to be claimed */
10620                 SendToICS(ics_prefix);
10621                 SendToICS("draw\n");
10622             } else if (StrCaseStr(resultDetails, "resign")) {
10623                 SendToICS(ics_prefix);
10624                 SendToICS("resign\n");
10625             }
10626         }
10627 #endif
10628         endingGame = 0; /* [HGM] crash */
10629         return;
10630     }
10631
10632     /* If we're loading the game from a file, stop */
10633     if (whosays == GE_FILE) {
10634       (void) StopLoadGameTimer();
10635       gameFileFP = NULL;
10636     }
10637
10638     /* Cancel draw offers */
10639     first.offeredDraw = second.offeredDraw = 0;
10640
10641     /* If this is an ICS game, only ICS can really say it's done;
10642        if not, anyone can. */
10643     isIcsGame = (gameMode == IcsPlayingWhite ||
10644                  gameMode == IcsPlayingBlack ||
10645                  gameMode == IcsObserving    ||
10646                  gameMode == IcsExamining);
10647
10648     if (!isIcsGame || whosays == GE_ICS) {
10649         /* OK -- not an ICS game, or ICS said it was done */
10650         StopClocks();
10651         if (!isIcsGame && !appData.noChessProgram)
10652           SetUserThinkingEnables();
10653
10654         /* [HGM] if a machine claims the game end we verify this claim */
10655         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10656             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10657                 char claimer;
10658                 ChessMove trueResult = (ChessMove) -1;
10659
10660                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10661                                             first.twoMachinesColor[0] :
10662                                             second.twoMachinesColor[0] ;
10663
10664                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10665                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10666                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10667                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10668                 } else
10669                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10670                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10671                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10672                 } else
10673                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10674                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10675                 }
10676
10677                 // now verify win claims, but not in drop games, as we don't understand those yet
10678                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10679                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10680                     (result == WhiteWins && claimer == 'w' ||
10681                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10682                       if (appData.debugMode) {
10683                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10684                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10685                       }
10686                       if(result != trueResult) {
10687                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10688                               result = claimer == 'w' ? BlackWins : WhiteWins;
10689                               resultDetails = buf;
10690                       }
10691                 } else
10692                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10693                     && (forwardMostMove <= backwardMostMove ||
10694                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10695                         (claimer=='b')==(forwardMostMove&1))
10696                                                                                   ) {
10697                       /* [HGM] verify: draws that were not flagged are false claims */
10698                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10699                       result = claimer == 'w' ? BlackWins : WhiteWins;
10700                       resultDetails = buf;
10701                 }
10702                 /* (Claiming a loss is accepted no questions asked!) */
10703             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10704                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10705                 result = GameUnfinished;
10706                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10707             }
10708             /* [HGM] bare: don't allow bare King to win */
10709             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10710                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10711                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10712                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10713                && result != GameIsDrawn)
10714             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10715                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10716                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10717                         if(p >= 0 && p <= (int)WhiteKing) k++;
10718                 }
10719                 if (appData.debugMode) {
10720                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10721                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10722                 }
10723                 if(k <= 1) {
10724                         result = GameIsDrawn;
10725                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10726                         resultDetails = buf;
10727                 }
10728             }
10729         }
10730
10731
10732         if(serverMoves != NULL && !loadFlag) { char c = '=';
10733             if(result==WhiteWins) c = '+';
10734             if(result==BlackWins) c = '-';
10735             if(resultDetails != NULL)
10736                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10737         }
10738         if (resultDetails != NULL) {
10739             gameInfo.result = result;
10740             gameInfo.resultDetails = StrSave(resultDetails);
10741
10742             /* display last move only if game was not loaded from file */
10743             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10744                 DisplayMove(currentMove - 1);
10745
10746             if (forwardMostMove != 0) {
10747                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10748                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10749                                                                 ) {
10750                     if (*appData.saveGameFile != NULLCHAR) {
10751                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10752                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10753                         else
10754                         SaveGameToFile(appData.saveGameFile, TRUE);
10755                     } else if (appData.autoSaveGames) {
10756                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10757                     }
10758                     if (*appData.savePositionFile != NULLCHAR) {
10759                         SavePositionToFile(appData.savePositionFile);
10760                     }
10761                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10762                 }
10763             }
10764
10765             /* Tell program how game ended in case it is learning */
10766             /* [HGM] Moved this to after saving the PGN, just in case */
10767             /* engine died and we got here through time loss. In that */
10768             /* case we will get a fatal error writing the pipe, which */
10769             /* would otherwise lose us the PGN.                       */
10770             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10771             /* output during GameEnds should never be fatal anymore   */
10772             if (gameMode == MachinePlaysWhite ||
10773                 gameMode == MachinePlaysBlack ||
10774                 gameMode == TwoMachinesPlay ||
10775                 gameMode == IcsPlayingWhite ||
10776                 gameMode == IcsPlayingBlack ||
10777                 gameMode == BeginningOfGame) {
10778                 char buf[MSG_SIZ];
10779                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10780                         resultDetails);
10781                 if (first.pr != NoProc) {
10782                     SendToProgram(buf, &first);
10783                 }
10784                 if (second.pr != NoProc &&
10785                     gameMode == TwoMachinesPlay) {
10786                     SendToProgram(buf, &second);
10787                 }
10788             }
10789         }
10790
10791         if (appData.icsActive) {
10792             if (appData.quietPlay &&
10793                 (gameMode == IcsPlayingWhite ||
10794                  gameMode == IcsPlayingBlack)) {
10795                 SendToICS(ics_prefix);
10796                 SendToICS("set shout 1\n");
10797             }
10798             nextGameMode = IcsIdle;
10799             ics_user_moved = FALSE;
10800             /* clean up premove.  It's ugly when the game has ended and the
10801              * premove highlights are still on the board.
10802              */
10803             if (gotPremove) {
10804               gotPremove = FALSE;
10805               ClearPremoveHighlights();
10806               DrawPosition(FALSE, boards[currentMove]);
10807             }
10808             if (whosays == GE_ICS) {
10809                 switch (result) {
10810                 case WhiteWins:
10811                     if (gameMode == IcsPlayingWhite)
10812                         PlayIcsWinSound();
10813                     else if(gameMode == IcsPlayingBlack)
10814                         PlayIcsLossSound();
10815                     break;
10816                 case BlackWins:
10817                     if (gameMode == IcsPlayingBlack)
10818                         PlayIcsWinSound();
10819                     else if(gameMode == IcsPlayingWhite)
10820                         PlayIcsLossSound();
10821                     break;
10822                 case GameIsDrawn:
10823                     PlayIcsDrawSound();
10824                     break;
10825                 default:
10826                     PlayIcsUnfinishedSound();
10827                 }
10828             }
10829         } else if (gameMode == EditGame ||
10830                    gameMode == PlayFromGameFile ||
10831                    gameMode == AnalyzeMode ||
10832                    gameMode == AnalyzeFile) {
10833             nextGameMode = gameMode;
10834         } else {
10835             nextGameMode = EndOfGame;
10836         }
10837         pausing = FALSE;
10838         ModeHighlight();
10839     } else {
10840         nextGameMode = gameMode;
10841     }
10842
10843     if (appData.noChessProgram) {
10844         gameMode = nextGameMode;
10845         ModeHighlight();
10846         endingGame = 0; /* [HGM] crash */
10847         return;
10848     }
10849
10850     if (first.reuse) {
10851         /* Put first chess program into idle state */
10852         if (first.pr != NoProc &&
10853             (gameMode == MachinePlaysWhite ||
10854              gameMode == MachinePlaysBlack ||
10855              gameMode == TwoMachinesPlay ||
10856              gameMode == IcsPlayingWhite ||
10857              gameMode == IcsPlayingBlack ||
10858              gameMode == BeginningOfGame)) {
10859             SendToProgram("force\n", &first);
10860             if (first.usePing) {
10861               char buf[MSG_SIZ];
10862               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10863               SendToProgram(buf, &first);
10864             }
10865         }
10866     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10867         /* Kill off first chess program */
10868         if (first.isr != NULL)
10869           RemoveInputSource(first.isr);
10870         first.isr = NULL;
10871
10872         if (first.pr != NoProc) {
10873             ExitAnalyzeMode();
10874             DoSleep( appData.delayBeforeQuit );
10875             SendToProgram("quit\n", &first);
10876             DoSleep( appData.delayAfterQuit );
10877             DestroyChildProcess(first.pr, first.useSigterm);
10878             first.reload = TRUE;
10879         }
10880         first.pr = NoProc;
10881     }
10882     if (second.reuse) {
10883         /* Put second chess program into idle state */
10884         if (second.pr != NoProc &&
10885             gameMode == TwoMachinesPlay) {
10886             SendToProgram("force\n", &second);
10887             if (second.usePing) {
10888               char buf[MSG_SIZ];
10889               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10890               SendToProgram(buf, &second);
10891             }
10892         }
10893     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10894         /* Kill off second chess program */
10895         if (second.isr != NULL)
10896           RemoveInputSource(second.isr);
10897         second.isr = NULL;
10898
10899         if (second.pr != NoProc) {
10900             DoSleep( appData.delayBeforeQuit );
10901             SendToProgram("quit\n", &second);
10902             DoSleep( appData.delayAfterQuit );
10903             DestroyChildProcess(second.pr, second.useSigterm);
10904             second.reload = TRUE;
10905         }
10906         second.pr = NoProc;
10907     }
10908
10909     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10910         char resChar = '=';
10911         switch (result) {
10912         case WhiteWins:
10913           resChar = '+';
10914           if (first.twoMachinesColor[0] == 'w') {
10915             first.matchWins++;
10916           } else {
10917             second.matchWins++;
10918           }
10919           break;
10920         case BlackWins:
10921           resChar = '-';
10922           if (first.twoMachinesColor[0] == 'b') {
10923             first.matchWins++;
10924           } else {
10925             second.matchWins++;
10926           }
10927           break;
10928         case GameUnfinished:
10929           resChar = ' ';
10930         default:
10931           break;
10932         }
10933
10934         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10935         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10936             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10937             ReserveGame(nextGame, resChar); // sets nextGame
10938             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10939             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10940         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10941
10942         if (nextGame <= appData.matchGames && !abortMatch) {
10943             gameMode = nextGameMode;
10944             matchGame = nextGame; // this will be overruled in tourney mode!
10945             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10946             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10947             endingGame = 0; /* [HGM] crash */
10948             return;
10949         } else {
10950             gameMode = nextGameMode;
10951             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10952                      first.tidy, second.tidy,
10953                      first.matchWins, second.matchWins,
10954                      appData.matchGames - (first.matchWins + second.matchWins));
10955             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10956             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10957             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10958             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10959                 first.twoMachinesColor = "black\n";
10960                 second.twoMachinesColor = "white\n";
10961             } else {
10962                 first.twoMachinesColor = "white\n";
10963                 second.twoMachinesColor = "black\n";
10964             }
10965         }
10966     }
10967     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10968         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10969       ExitAnalyzeMode();
10970     gameMode = nextGameMode;
10971     ModeHighlight();
10972     endingGame = 0;  /* [HGM] crash */
10973     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10974         if(matchMode == TRUE) { // match through command line: exit with or without popup
10975             if(ranking) {
10976                 ToNrEvent(forwardMostMove);
10977                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10978                 else ExitEvent(0);
10979             } else DisplayFatalError(buf, 0, 0);
10980         } else { // match through menu; just stop, with or without popup
10981             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10982             ModeHighlight();
10983             if(ranking){
10984                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10985             } else DisplayNote(buf);
10986       }
10987       if(ranking) free(ranking);
10988     }
10989 }
10990
10991 /* Assumes program was just initialized (initString sent).
10992    Leaves program in force mode. */
10993 void
10994 FeedMovesToProgram (ChessProgramState *cps, int upto)
10995 {
10996     int i;
10997
10998     if (appData.debugMode)
10999       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11000               startedFromSetupPosition ? "position and " : "",
11001               backwardMostMove, upto, cps->which);
11002     if(currentlyInitializedVariant != gameInfo.variant) {
11003       char buf[MSG_SIZ];
11004         // [HGM] variantswitch: make engine aware of new variant
11005         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11006                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11007         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11008         SendToProgram(buf, cps);
11009         currentlyInitializedVariant = gameInfo.variant;
11010     }
11011     SendToProgram("force\n", cps);
11012     if (startedFromSetupPosition) {
11013         SendBoard(cps, backwardMostMove);
11014     if (appData.debugMode) {
11015         fprintf(debugFP, "feedMoves\n");
11016     }
11017     }
11018     for (i = backwardMostMove; i < upto; i++) {
11019         SendMoveToProgram(i, cps);
11020     }
11021 }
11022
11023
11024 int
11025 ResurrectChessProgram ()
11026 {
11027      /* The chess program may have exited.
11028         If so, restart it and feed it all the moves made so far. */
11029     static int doInit = 0;
11030
11031     if (appData.noChessProgram) return 1;
11032
11033     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11034         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11035         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11036         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11037     } else {
11038         if (first.pr != NoProc) return 1;
11039         StartChessProgram(&first);
11040     }
11041     InitChessProgram(&first, FALSE);
11042     FeedMovesToProgram(&first, currentMove);
11043
11044     if (!first.sendTime) {
11045         /* can't tell gnuchess what its clock should read,
11046            so we bow to its notion. */
11047         ResetClocks();
11048         timeRemaining[0][currentMove] = whiteTimeRemaining;
11049         timeRemaining[1][currentMove] = blackTimeRemaining;
11050     }
11051
11052     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11053                 appData.icsEngineAnalyze) && first.analysisSupport) {
11054       SendToProgram("analyze\n", &first);
11055       first.analyzing = TRUE;
11056     }
11057     return 1;
11058 }
11059
11060 /*
11061  * Button procedures
11062  */
11063 void
11064 Reset (int redraw, int init)
11065 {
11066     int i;
11067
11068     if (appData.debugMode) {
11069         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11070                 redraw, init, gameMode);
11071     }
11072     CleanupTail(); // [HGM] vari: delete any stored variations
11073     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11074     pausing = pauseExamInvalid = FALSE;
11075     startedFromSetupPosition = blackPlaysFirst = FALSE;
11076     firstMove = TRUE;
11077     whiteFlag = blackFlag = FALSE;
11078     userOfferedDraw = FALSE;
11079     hintRequested = bookRequested = FALSE;
11080     first.maybeThinking = FALSE;
11081     second.maybeThinking = FALSE;
11082     first.bookSuspend = FALSE; // [HGM] book
11083     second.bookSuspend = FALSE;
11084     thinkOutput[0] = NULLCHAR;
11085     lastHint[0] = NULLCHAR;
11086     ClearGameInfo(&gameInfo);
11087     gameInfo.variant = StringToVariant(appData.variant);
11088     ics_user_moved = ics_clock_paused = FALSE;
11089     ics_getting_history = H_FALSE;
11090     ics_gamenum = -1;
11091     white_holding[0] = black_holding[0] = NULLCHAR;
11092     ClearProgramStats();
11093     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11094
11095     ResetFrontEnd();
11096     ClearHighlights();
11097     flipView = appData.flipView;
11098     ClearPremoveHighlights();
11099     gotPremove = FALSE;
11100     alarmSounded = FALSE;
11101
11102     GameEnds(EndOfFile, NULL, GE_PLAYER);
11103     if(appData.serverMovesName != NULL) {
11104         /* [HGM] prepare to make moves file for broadcasting */
11105         clock_t t = clock();
11106         if(serverMoves != NULL) fclose(serverMoves);
11107         serverMoves = fopen(appData.serverMovesName, "r");
11108         if(serverMoves != NULL) {
11109             fclose(serverMoves);
11110             /* delay 15 sec before overwriting, so all clients can see end */
11111             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11112         }
11113         serverMoves = fopen(appData.serverMovesName, "w");
11114     }
11115
11116     ExitAnalyzeMode();
11117     gameMode = BeginningOfGame;
11118     ModeHighlight();
11119     if(appData.icsActive) gameInfo.variant = VariantNormal;
11120     currentMove = forwardMostMove = backwardMostMove = 0;
11121     MarkTargetSquares(1);
11122     InitPosition(redraw);
11123     for (i = 0; i < MAX_MOVES; i++) {
11124         if (commentList[i] != NULL) {
11125             free(commentList[i]);
11126             commentList[i] = NULL;
11127         }
11128     }
11129     ResetClocks();
11130     timeRemaining[0][0] = whiteTimeRemaining;
11131     timeRemaining[1][0] = blackTimeRemaining;
11132
11133     if (first.pr == NoProc) {
11134         StartChessProgram(&first);
11135     }
11136     if (init) {
11137             InitChessProgram(&first, startedFromSetupPosition);
11138     }
11139     DisplayTitle("");
11140     DisplayMessage("", "");
11141     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11142     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11143     ClearMap();        // [HGM] exclude: invalidate map
11144 }
11145
11146 void
11147 AutoPlayGameLoop ()
11148 {
11149     for (;;) {
11150         if (!AutoPlayOneMove())
11151           return;
11152         if (matchMode || appData.timeDelay == 0)
11153           continue;
11154         if (appData.timeDelay < 0)
11155           return;
11156         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11157         break;
11158     }
11159 }
11160
11161 void
11162 AnalyzeNextGame()
11163 {
11164     ReloadGame(1); // next game
11165 }
11166
11167 int
11168 AutoPlayOneMove ()
11169 {
11170     int fromX, fromY, toX, toY;
11171
11172     if (appData.debugMode) {
11173       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11174     }
11175
11176     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11177       return FALSE;
11178
11179     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11180       pvInfoList[currentMove].depth = programStats.depth;
11181       pvInfoList[currentMove].score = programStats.score;
11182       pvInfoList[currentMove].time  = 0;
11183       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11184     }
11185
11186     if (currentMove >= forwardMostMove) {
11187       if(gameMode == AnalyzeFile) {
11188           if(appData.loadGameIndex == -1) {
11189             GameEnds(EndOfFile, NULL, GE_FILE);
11190           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11191           } else {
11192           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11193         }
11194       }
11195 //      gameMode = EndOfGame;
11196 //      ModeHighlight();
11197
11198       /* [AS] Clear current move marker at the end of a game */
11199       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11200
11201       return FALSE;
11202     }
11203
11204     toX = moveList[currentMove][2] - AAA;
11205     toY = moveList[currentMove][3] - ONE;
11206
11207     if (moveList[currentMove][1] == '@') {
11208         if (appData.highlightLastMove) {
11209             SetHighlights(-1, -1, toX, toY);
11210         }
11211     } else {
11212         fromX = moveList[currentMove][0] - AAA;
11213         fromY = moveList[currentMove][1] - ONE;
11214
11215         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11216
11217         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11218
11219         if (appData.highlightLastMove) {
11220             SetHighlights(fromX, fromY, toX, toY);
11221         }
11222     }
11223     DisplayMove(currentMove);
11224     SendMoveToProgram(currentMove++, &first);
11225     DisplayBothClocks();
11226     DrawPosition(FALSE, boards[currentMove]);
11227     // [HGM] PV info: always display, routine tests if empty
11228     DisplayComment(currentMove - 1, commentList[currentMove]);
11229     return TRUE;
11230 }
11231
11232
11233 int
11234 LoadGameOneMove (ChessMove readAhead)
11235 {
11236     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11237     char promoChar = NULLCHAR;
11238     ChessMove moveType;
11239     char move[MSG_SIZ];
11240     char *p, *q;
11241
11242     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11243         gameMode != AnalyzeMode && gameMode != Training) {
11244         gameFileFP = NULL;
11245         return FALSE;
11246     }
11247
11248     yyboardindex = forwardMostMove;
11249     if (readAhead != EndOfFile) {
11250       moveType = readAhead;
11251     } else {
11252       if (gameFileFP == NULL)
11253           return FALSE;
11254       moveType = (ChessMove) Myylex();
11255     }
11256
11257     done = FALSE;
11258     switch (moveType) {
11259       case Comment:
11260         if (appData.debugMode)
11261           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11262         p = yy_text;
11263
11264         /* append the comment but don't display it */
11265         AppendComment(currentMove, p, FALSE);
11266         return TRUE;
11267
11268       case WhiteCapturesEnPassant:
11269       case BlackCapturesEnPassant:
11270       case WhitePromotion:
11271       case BlackPromotion:
11272       case WhiteNonPromotion:
11273       case BlackNonPromotion:
11274       case NormalMove:
11275       case WhiteKingSideCastle:
11276       case WhiteQueenSideCastle:
11277       case BlackKingSideCastle:
11278       case BlackQueenSideCastle:
11279       case WhiteKingSideCastleWild:
11280       case WhiteQueenSideCastleWild:
11281       case BlackKingSideCastleWild:
11282       case BlackQueenSideCastleWild:
11283       /* PUSH Fabien */
11284       case WhiteHSideCastleFR:
11285       case WhiteASideCastleFR:
11286       case BlackHSideCastleFR:
11287       case BlackASideCastleFR:
11288       /* POP Fabien */
11289         if (appData.debugMode)
11290           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11291         fromX = currentMoveString[0] - AAA;
11292         fromY = currentMoveString[1] - ONE;
11293         toX = currentMoveString[2] - AAA;
11294         toY = currentMoveString[3] - ONE;
11295         promoChar = currentMoveString[4];
11296         break;
11297
11298       case WhiteDrop:
11299       case BlackDrop:
11300         if (appData.debugMode)
11301           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11302         fromX = moveType == WhiteDrop ?
11303           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11304         (int) CharToPiece(ToLower(currentMoveString[0]));
11305         fromY = DROP_RANK;
11306         toX = currentMoveString[2] - AAA;
11307         toY = currentMoveString[3] - ONE;
11308         break;
11309
11310       case WhiteWins:
11311       case BlackWins:
11312       case GameIsDrawn:
11313       case GameUnfinished:
11314         if (appData.debugMode)
11315           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11316         p = strchr(yy_text, '{');
11317         if (p == NULL) p = strchr(yy_text, '(');
11318         if (p == NULL) {
11319             p = yy_text;
11320             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11321         } else {
11322             q = strchr(p, *p == '{' ? '}' : ')');
11323             if (q != NULL) *q = NULLCHAR;
11324             p++;
11325         }
11326         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11327         GameEnds(moveType, p, GE_FILE);
11328         done = TRUE;
11329         if (cmailMsgLoaded) {
11330             ClearHighlights();
11331             flipView = WhiteOnMove(currentMove);
11332             if (moveType == GameUnfinished) flipView = !flipView;
11333             if (appData.debugMode)
11334               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11335         }
11336         break;
11337
11338       case EndOfFile:
11339         if (appData.debugMode)
11340           fprintf(debugFP, "Parser hit end of file\n");
11341         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11342           case MT_NONE:
11343           case MT_CHECK:
11344             break;
11345           case MT_CHECKMATE:
11346           case MT_STAINMATE:
11347             if (WhiteOnMove(currentMove)) {
11348                 GameEnds(BlackWins, "Black mates", GE_FILE);
11349             } else {
11350                 GameEnds(WhiteWins, "White mates", GE_FILE);
11351             }
11352             break;
11353           case MT_STALEMATE:
11354             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11355             break;
11356         }
11357         done = TRUE;
11358         break;
11359
11360       case MoveNumberOne:
11361         if (lastLoadGameStart == GNUChessGame) {
11362             /* GNUChessGames have numbers, but they aren't move numbers */
11363             if (appData.debugMode)
11364               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11365                       yy_text, (int) moveType);
11366             return LoadGameOneMove(EndOfFile); /* tail recursion */
11367         }
11368         /* else fall thru */
11369
11370       case XBoardGame:
11371       case GNUChessGame:
11372       case PGNTag:
11373         /* Reached start of next game in file */
11374         if (appData.debugMode)
11375           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11376         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11377           case MT_NONE:
11378           case MT_CHECK:
11379             break;
11380           case MT_CHECKMATE:
11381           case MT_STAINMATE:
11382             if (WhiteOnMove(currentMove)) {
11383                 GameEnds(BlackWins, "Black mates", GE_FILE);
11384             } else {
11385                 GameEnds(WhiteWins, "White mates", GE_FILE);
11386             }
11387             break;
11388           case MT_STALEMATE:
11389             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11390             break;
11391         }
11392         done = TRUE;
11393         break;
11394
11395       case PositionDiagram:     /* should not happen; ignore */
11396       case ElapsedTime:         /* ignore */
11397       case NAG:                 /* ignore */
11398         if (appData.debugMode)
11399           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11400                   yy_text, (int) moveType);
11401         return LoadGameOneMove(EndOfFile); /* tail recursion */
11402
11403       case IllegalMove:
11404         if (appData.testLegality) {
11405             if (appData.debugMode)
11406               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11407             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11408                     (forwardMostMove / 2) + 1,
11409                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11410             DisplayError(move, 0);
11411             done = TRUE;
11412         } else {
11413             if (appData.debugMode)
11414               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11415                       yy_text, currentMoveString);
11416             fromX = currentMoveString[0] - AAA;
11417             fromY = currentMoveString[1] - ONE;
11418             toX = currentMoveString[2] - AAA;
11419             toY = currentMoveString[3] - ONE;
11420             promoChar = currentMoveString[4];
11421         }
11422         break;
11423
11424       case AmbiguousMove:
11425         if (appData.debugMode)
11426           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11427         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11428                 (forwardMostMove / 2) + 1,
11429                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11430         DisplayError(move, 0);
11431         done = TRUE;
11432         break;
11433
11434       default:
11435       case ImpossibleMove:
11436         if (appData.debugMode)
11437           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11438         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11439                 (forwardMostMove / 2) + 1,
11440                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11441         DisplayError(move, 0);
11442         done = TRUE;
11443         break;
11444     }
11445
11446     if (done) {
11447         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11448             DrawPosition(FALSE, boards[currentMove]);
11449             DisplayBothClocks();
11450             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11451               DisplayComment(currentMove - 1, commentList[currentMove]);
11452         }
11453         (void) StopLoadGameTimer();
11454         gameFileFP = NULL;
11455         cmailOldMove = forwardMostMove;
11456         return FALSE;
11457     } else {
11458         /* currentMoveString is set as a side-effect of yylex */
11459
11460         thinkOutput[0] = NULLCHAR;
11461         MakeMove(fromX, fromY, toX, toY, promoChar);
11462         currentMove = forwardMostMove;
11463         return TRUE;
11464     }
11465 }
11466
11467 /* Load the nth game from the given file */
11468 int
11469 LoadGameFromFile (char *filename, int n, char *title, int useList)
11470 {
11471     FILE *f;
11472     char buf[MSG_SIZ];
11473
11474     if (strcmp(filename, "-") == 0) {
11475         f = stdin;
11476         title = "stdin";
11477     } else {
11478         f = fopen(filename, "rb");
11479         if (f == NULL) {
11480           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11481             DisplayError(buf, errno);
11482             return FALSE;
11483         }
11484     }
11485     if (fseek(f, 0, 0) == -1) {
11486         /* f is not seekable; probably a pipe */
11487         useList = FALSE;
11488     }
11489     if (useList && n == 0) {
11490         int error = GameListBuild(f);
11491         if (error) {
11492             DisplayError(_("Cannot build game list"), error);
11493         } else if (!ListEmpty(&gameList) &&
11494                    ((ListGame *) gameList.tailPred)->number > 1) {
11495             GameListPopUp(f, title);
11496             return TRUE;
11497         }
11498         GameListDestroy();
11499         n = 1;
11500     }
11501     if (n == 0) n = 1;
11502     return LoadGame(f, n, title, FALSE);
11503 }
11504
11505
11506 void
11507 MakeRegisteredMove ()
11508 {
11509     int fromX, fromY, toX, toY;
11510     char promoChar;
11511     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11512         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11513           case CMAIL_MOVE:
11514           case CMAIL_DRAW:
11515             if (appData.debugMode)
11516               fprintf(debugFP, "Restoring %s for game %d\n",
11517                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11518
11519             thinkOutput[0] = NULLCHAR;
11520             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11521             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11522             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11523             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11524             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11525             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11526             MakeMove(fromX, fromY, toX, toY, promoChar);
11527             ShowMove(fromX, fromY, toX, toY);
11528
11529             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11530               case MT_NONE:
11531               case MT_CHECK:
11532                 break;
11533
11534               case MT_CHECKMATE:
11535               case MT_STAINMATE:
11536                 if (WhiteOnMove(currentMove)) {
11537                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11538                 } else {
11539                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11540                 }
11541                 break;
11542
11543               case MT_STALEMATE:
11544                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11545                 break;
11546             }
11547
11548             break;
11549
11550           case CMAIL_RESIGN:
11551             if (WhiteOnMove(currentMove)) {
11552                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11553             } else {
11554                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11555             }
11556             break;
11557
11558           case CMAIL_ACCEPT:
11559             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11560             break;
11561
11562           default:
11563             break;
11564         }
11565     }
11566
11567     return;
11568 }
11569
11570 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11571 int
11572 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11573 {
11574     int retVal;
11575
11576     if (gameNumber > nCmailGames) {
11577         DisplayError(_("No more games in this message"), 0);
11578         return FALSE;
11579     }
11580     if (f == lastLoadGameFP) {
11581         int offset = gameNumber - lastLoadGameNumber;
11582         if (offset == 0) {
11583             cmailMsg[0] = NULLCHAR;
11584             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11585                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11586                 nCmailMovesRegistered--;
11587             }
11588             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11589             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11590                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11591             }
11592         } else {
11593             if (! RegisterMove()) return FALSE;
11594         }
11595     }
11596
11597     retVal = LoadGame(f, gameNumber, title, useList);
11598
11599     /* Make move registered during previous look at this game, if any */
11600     MakeRegisteredMove();
11601
11602     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11603         commentList[currentMove]
11604           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11605         DisplayComment(currentMove - 1, commentList[currentMove]);
11606     }
11607
11608     return retVal;
11609 }
11610
11611 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11612 int
11613 ReloadGame (int offset)
11614 {
11615     int gameNumber = lastLoadGameNumber + offset;
11616     if (lastLoadGameFP == NULL) {
11617         DisplayError(_("No game has been loaded yet"), 0);
11618         return FALSE;
11619     }
11620     if (gameNumber <= 0) {
11621         DisplayError(_("Can't back up any further"), 0);
11622         return FALSE;
11623     }
11624     if (cmailMsgLoaded) {
11625         return CmailLoadGame(lastLoadGameFP, gameNumber,
11626                              lastLoadGameTitle, lastLoadGameUseList);
11627     } else {
11628         return LoadGame(lastLoadGameFP, gameNumber,
11629                         lastLoadGameTitle, lastLoadGameUseList);
11630     }
11631 }
11632
11633 int keys[EmptySquare+1];
11634
11635 int
11636 PositionMatches (Board b1, Board b2)
11637 {
11638     int r, f, sum=0;
11639     switch(appData.searchMode) {
11640         case 1: return CompareWithRights(b1, b2);
11641         case 2:
11642             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11643                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11644             }
11645             return TRUE;
11646         case 3:
11647             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11648               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11649                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11650             }
11651             return sum==0;
11652         case 4:
11653             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11654                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11655             }
11656             return sum==0;
11657     }
11658     return TRUE;
11659 }
11660
11661 #define Q_PROMO  4
11662 #define Q_EP     3
11663 #define Q_BCASTL 2
11664 #define Q_WCASTL 1
11665
11666 int pieceList[256], quickBoard[256];
11667 ChessSquare pieceType[256] = { EmptySquare };
11668 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11669 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11670 int soughtTotal, turn;
11671 Boolean epOK, flipSearch;
11672
11673 typedef struct {
11674     unsigned char piece, to;
11675 } Move;
11676
11677 #define DSIZE (250000)
11678
11679 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11680 Move *moveDatabase = initialSpace;
11681 unsigned int movePtr, dataSize = DSIZE;
11682
11683 int
11684 MakePieceList (Board board, int *counts)
11685 {
11686     int r, f, n=Q_PROMO, total=0;
11687     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11688     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11689         int sq = f + (r<<4);
11690         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11691             quickBoard[sq] = ++n;
11692             pieceList[n] = sq;
11693             pieceType[n] = board[r][f];
11694             counts[board[r][f]]++;
11695             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11696             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11697             total++;
11698         }
11699     }
11700     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11701     return total;
11702 }
11703
11704 void
11705 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11706 {
11707     int sq = fromX + (fromY<<4);
11708     int piece = quickBoard[sq];
11709     quickBoard[sq] = 0;
11710     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11711     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11712         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11713         moveDatabase[movePtr++].piece = Q_WCASTL;
11714         quickBoard[sq] = piece;
11715         piece = quickBoard[from]; quickBoard[from] = 0;
11716         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11717     } else
11718     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11719         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11720         moveDatabase[movePtr++].piece = Q_BCASTL;
11721         quickBoard[sq] = piece;
11722         piece = quickBoard[from]; quickBoard[from] = 0;
11723         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11724     } else
11725     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11726         quickBoard[(fromY<<4)+toX] = 0;
11727         moveDatabase[movePtr].piece = Q_EP;
11728         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11729         moveDatabase[movePtr].to = sq;
11730     } else
11731     if(promoPiece != pieceType[piece]) {
11732         moveDatabase[movePtr++].piece = Q_PROMO;
11733         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11734     }
11735     moveDatabase[movePtr].piece = piece;
11736     quickBoard[sq] = piece;
11737     movePtr++;
11738 }
11739
11740 int
11741 PackGame (Board board)
11742 {
11743     Move *newSpace = NULL;
11744     moveDatabase[movePtr].piece = 0; // terminate previous game
11745     if(movePtr > dataSize) {
11746         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11747         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11748         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11749         if(newSpace) {
11750             int i;
11751             Move *p = moveDatabase, *q = newSpace;
11752             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11753             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11754             moveDatabase = newSpace;
11755         } else { // calloc failed, we must be out of memory. Too bad...
11756             dataSize = 0; // prevent calloc events for all subsequent games
11757             return 0;     // and signal this one isn't cached
11758         }
11759     }
11760     movePtr++;
11761     MakePieceList(board, counts);
11762     return movePtr;
11763 }
11764
11765 int
11766 QuickCompare (Board board, int *minCounts, int *maxCounts)
11767 {   // compare according to search mode
11768     int r, f;
11769     switch(appData.searchMode)
11770     {
11771       case 1: // exact position match
11772         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11773         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11774             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11775         }
11776         break;
11777       case 2: // can have extra material on empty squares
11778         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11779             if(board[r][f] == EmptySquare) continue;
11780             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11781         }
11782         break;
11783       case 3: // material with exact Pawn structure
11784         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11785             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11786             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11787         } // fall through to material comparison
11788       case 4: // exact material
11789         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11790         break;
11791       case 6: // material range with given imbalance
11792         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11793         // fall through to range comparison
11794       case 5: // material range
11795         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11796     }
11797     return TRUE;
11798 }
11799
11800 int
11801 QuickScan (Board board, Move *move)
11802 {   // reconstruct game,and compare all positions in it
11803     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11804     do {
11805         int piece = move->piece;
11806         int to = move->to, from = pieceList[piece];
11807         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11808           if(!piece) return -1;
11809           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11810             piece = (++move)->piece;
11811             from = pieceList[piece];
11812             counts[pieceType[piece]]--;
11813             pieceType[piece] = (ChessSquare) move->to;
11814             counts[move->to]++;
11815           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11816             counts[pieceType[quickBoard[to]]]--;
11817             quickBoard[to] = 0; total--;
11818             move++;
11819             continue;
11820           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11821             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11822             from  = pieceList[piece]; // so this must be King
11823             quickBoard[from] = 0;
11824             pieceList[piece] = to;
11825             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11826             quickBoard[from] = 0; // rook
11827             quickBoard[to] = piece;
11828             to = move->to; piece = move->piece;
11829             goto aftercastle;
11830           }
11831         }
11832         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11833         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11834         quickBoard[from] = 0;
11835       aftercastle:
11836         quickBoard[to] = piece;
11837         pieceList[piece] = to;
11838         cnt++; turn ^= 3;
11839         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11840            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11841            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11842                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11843           ) {
11844             static int lastCounts[EmptySquare+1];
11845             int i;
11846             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11847             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11848         } else stretch = 0;
11849         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11850         move++;
11851     } while(1);
11852 }
11853
11854 void
11855 InitSearch ()
11856 {
11857     int r, f;
11858     flipSearch = FALSE;
11859     CopyBoard(soughtBoard, boards[currentMove]);
11860     soughtTotal = MakePieceList(soughtBoard, maxSought);
11861     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11862     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11863     CopyBoard(reverseBoard, boards[currentMove]);
11864     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11865         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11866         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11867         reverseBoard[r][f] = piece;
11868     }
11869     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11870     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11871     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11872                  || (boards[currentMove][CASTLING][2] == NoRights ||
11873                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11874                  && (boards[currentMove][CASTLING][5] == NoRights ||
11875                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11876       ) {
11877         flipSearch = TRUE;
11878         CopyBoard(flipBoard, soughtBoard);
11879         CopyBoard(rotateBoard, reverseBoard);
11880         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11881             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11882             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11883         }
11884     }
11885     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11886     if(appData.searchMode >= 5) {
11887         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11888         MakePieceList(soughtBoard, minSought);
11889         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11890     }
11891     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11892         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11893 }
11894
11895 GameInfo dummyInfo;
11896 static int creatingBook;
11897
11898 int
11899 GameContainsPosition (FILE *f, ListGame *lg)
11900 {
11901     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11902     int fromX, fromY, toX, toY;
11903     char promoChar;
11904     static int initDone=FALSE;
11905
11906     // weed out games based on numerical tag comparison
11907     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11908     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11909     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11910     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11911     if(!initDone) {
11912         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11913         initDone = TRUE;
11914     }
11915     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11916     else CopyBoard(boards[scratch], initialPosition); // default start position
11917     if(lg->moves) {
11918         turn = btm + 1;
11919         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11920         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11921     }
11922     if(btm) plyNr++;
11923     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11924     fseek(f, lg->offset, 0);
11925     yynewfile(f);
11926     while(1) {
11927         yyboardindex = scratch;
11928         quickFlag = plyNr+1;
11929         next = Myylex();
11930         quickFlag = 0;
11931         switch(next) {
11932             case PGNTag:
11933                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11934             default:
11935                 continue;
11936
11937             case XBoardGame:
11938             case GNUChessGame:
11939                 if(plyNr) return -1; // after we have seen moves, this is for new game
11940               continue;
11941
11942             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11943             case ImpossibleMove:
11944             case WhiteWins: // game ends here with these four
11945             case BlackWins:
11946             case GameIsDrawn:
11947             case GameUnfinished:
11948                 return -1;
11949
11950             case IllegalMove:
11951                 if(appData.testLegality) return -1;
11952             case WhiteCapturesEnPassant:
11953             case BlackCapturesEnPassant:
11954             case WhitePromotion:
11955             case BlackPromotion:
11956             case WhiteNonPromotion:
11957             case BlackNonPromotion:
11958             case NormalMove:
11959             case WhiteKingSideCastle:
11960             case WhiteQueenSideCastle:
11961             case BlackKingSideCastle:
11962             case BlackQueenSideCastle:
11963             case WhiteKingSideCastleWild:
11964             case WhiteQueenSideCastleWild:
11965             case BlackKingSideCastleWild:
11966             case BlackQueenSideCastleWild:
11967             case WhiteHSideCastleFR:
11968             case WhiteASideCastleFR:
11969             case BlackHSideCastleFR:
11970             case BlackASideCastleFR:
11971                 fromX = currentMoveString[0] - AAA;
11972                 fromY = currentMoveString[1] - ONE;
11973                 toX = currentMoveString[2] - AAA;
11974                 toY = currentMoveString[3] - ONE;
11975                 promoChar = currentMoveString[4];
11976                 break;
11977             case WhiteDrop:
11978             case BlackDrop:
11979                 fromX = next == WhiteDrop ?
11980                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11981                   (int) CharToPiece(ToLower(currentMoveString[0]));
11982                 fromY = DROP_RANK;
11983                 toX = currentMoveString[2] - AAA;
11984                 toY = currentMoveString[3] - ONE;
11985                 promoChar = 0;
11986                 break;
11987         }
11988         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11989         plyNr++;
11990         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11991         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11992         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11993         if(appData.findMirror) {
11994             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11995             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11996         }
11997     }
11998 }
11999
12000 /* Load the nth game from open file f */
12001 int
12002 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12003 {
12004     ChessMove cm;
12005     char buf[MSG_SIZ];
12006     int gn = gameNumber;
12007     ListGame *lg = NULL;
12008     int numPGNTags = 0;
12009     int err, pos = -1;
12010     GameMode oldGameMode;
12011     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12012
12013     if (appData.debugMode)
12014         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12015
12016     if (gameMode == Training )
12017         SetTrainingModeOff();
12018
12019     oldGameMode = gameMode;
12020     if (gameMode != BeginningOfGame) {
12021       Reset(FALSE, TRUE);
12022     }
12023
12024     gameFileFP = f;
12025     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12026         fclose(lastLoadGameFP);
12027     }
12028
12029     if (useList) {
12030         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12031
12032         if (lg) {
12033             fseek(f, lg->offset, 0);
12034             GameListHighlight(gameNumber);
12035             pos = lg->position;
12036             gn = 1;
12037         }
12038         else {
12039             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12040               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12041             else
12042             DisplayError(_("Game number out of range"), 0);
12043             return FALSE;
12044         }
12045     } else {
12046         GameListDestroy();
12047         if (fseek(f, 0, 0) == -1) {
12048             if (f == lastLoadGameFP ?
12049                 gameNumber == lastLoadGameNumber + 1 :
12050                 gameNumber == 1) {
12051                 gn = 1;
12052             } else {
12053                 DisplayError(_("Can't seek on game file"), 0);
12054                 return FALSE;
12055             }
12056         }
12057     }
12058     lastLoadGameFP = f;
12059     lastLoadGameNumber = gameNumber;
12060     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12061     lastLoadGameUseList = useList;
12062
12063     yynewfile(f);
12064
12065     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12066       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12067                 lg->gameInfo.black);
12068             DisplayTitle(buf);
12069     } else if (*title != NULLCHAR) {
12070         if (gameNumber > 1) {
12071           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12072             DisplayTitle(buf);
12073         } else {
12074             DisplayTitle(title);
12075         }
12076     }
12077
12078     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12079         gameMode = PlayFromGameFile;
12080         ModeHighlight();
12081     }
12082
12083     currentMove = forwardMostMove = backwardMostMove = 0;
12084     CopyBoard(boards[0], initialPosition);
12085     StopClocks();
12086
12087     /*
12088      * Skip the first gn-1 games in the file.
12089      * Also skip over anything that precedes an identifiable
12090      * start of game marker, to avoid being confused by
12091      * garbage at the start of the file.  Currently
12092      * recognized start of game markers are the move number "1",
12093      * the pattern "gnuchess .* game", the pattern
12094      * "^[#;%] [^ ]* game file", and a PGN tag block.
12095      * A game that starts with one of the latter two patterns
12096      * will also have a move number 1, possibly
12097      * following a position diagram.
12098      * 5-4-02: Let's try being more lenient and allowing a game to
12099      * start with an unnumbered move.  Does that break anything?
12100      */
12101     cm = lastLoadGameStart = EndOfFile;
12102     while (gn > 0) {
12103         yyboardindex = forwardMostMove;
12104         cm = (ChessMove) Myylex();
12105         switch (cm) {
12106           case EndOfFile:
12107             if (cmailMsgLoaded) {
12108                 nCmailGames = CMAIL_MAX_GAMES - gn;
12109             } else {
12110                 Reset(TRUE, TRUE);
12111                 DisplayError(_("Game not found in file"), 0);
12112             }
12113             return FALSE;
12114
12115           case GNUChessGame:
12116           case XBoardGame:
12117             gn--;
12118             lastLoadGameStart = cm;
12119             break;
12120
12121           case MoveNumberOne:
12122             switch (lastLoadGameStart) {
12123               case GNUChessGame:
12124               case XBoardGame:
12125               case PGNTag:
12126                 break;
12127               case MoveNumberOne:
12128               case EndOfFile:
12129                 gn--;           /* count this game */
12130                 lastLoadGameStart = cm;
12131                 break;
12132               default:
12133                 /* impossible */
12134                 break;
12135             }
12136             break;
12137
12138           case PGNTag:
12139             switch (lastLoadGameStart) {
12140               case GNUChessGame:
12141               case PGNTag:
12142               case MoveNumberOne:
12143               case EndOfFile:
12144                 gn--;           /* count this game */
12145                 lastLoadGameStart = cm;
12146                 break;
12147               case XBoardGame:
12148                 lastLoadGameStart = cm; /* game counted already */
12149                 break;
12150               default:
12151                 /* impossible */
12152                 break;
12153             }
12154             if (gn > 0) {
12155                 do {
12156                     yyboardindex = forwardMostMove;
12157                     cm = (ChessMove) Myylex();
12158                 } while (cm == PGNTag || cm == Comment);
12159             }
12160             break;
12161
12162           case WhiteWins:
12163           case BlackWins:
12164           case GameIsDrawn:
12165             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12166                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12167                     != CMAIL_OLD_RESULT) {
12168                     nCmailResults ++ ;
12169                     cmailResult[  CMAIL_MAX_GAMES
12170                                 - gn - 1] = CMAIL_OLD_RESULT;
12171                 }
12172             }
12173             break;
12174
12175           case NormalMove:
12176             /* Only a NormalMove can be at the start of a game
12177              * without a position diagram. */
12178             if (lastLoadGameStart == EndOfFile ) {
12179               gn--;
12180               lastLoadGameStart = MoveNumberOne;
12181             }
12182             break;
12183
12184           default:
12185             break;
12186         }
12187     }
12188
12189     if (appData.debugMode)
12190       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12191
12192     if (cm == XBoardGame) {
12193         /* Skip any header junk before position diagram and/or move 1 */
12194         for (;;) {
12195             yyboardindex = forwardMostMove;
12196             cm = (ChessMove) Myylex();
12197
12198             if (cm == EndOfFile ||
12199                 cm == GNUChessGame || cm == XBoardGame) {
12200                 /* Empty game; pretend end-of-file and handle later */
12201                 cm = EndOfFile;
12202                 break;
12203             }
12204
12205             if (cm == MoveNumberOne || cm == PositionDiagram ||
12206                 cm == PGNTag || cm == Comment)
12207               break;
12208         }
12209     } else if (cm == GNUChessGame) {
12210         if (gameInfo.event != NULL) {
12211             free(gameInfo.event);
12212         }
12213         gameInfo.event = StrSave(yy_text);
12214     }
12215
12216     startedFromSetupPosition = FALSE;
12217     while (cm == PGNTag) {
12218         if (appData.debugMode)
12219           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12220         err = ParsePGNTag(yy_text, &gameInfo);
12221         if (!err) numPGNTags++;
12222
12223         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12224         if(gameInfo.variant != oldVariant) {
12225             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12226             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12227             InitPosition(TRUE);
12228             oldVariant = gameInfo.variant;
12229             if (appData.debugMode)
12230               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12231         }
12232
12233
12234         if (gameInfo.fen != NULL) {
12235           Board initial_position;
12236           startedFromSetupPosition = TRUE;
12237           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12238             Reset(TRUE, TRUE);
12239             DisplayError(_("Bad FEN position in file"), 0);
12240             return FALSE;
12241           }
12242           CopyBoard(boards[0], initial_position);
12243           if (blackPlaysFirst) {
12244             currentMove = forwardMostMove = backwardMostMove = 1;
12245             CopyBoard(boards[1], initial_position);
12246             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12247             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12248             timeRemaining[0][1] = whiteTimeRemaining;
12249             timeRemaining[1][1] = blackTimeRemaining;
12250             if (commentList[0] != NULL) {
12251               commentList[1] = commentList[0];
12252               commentList[0] = NULL;
12253             }
12254           } else {
12255             currentMove = forwardMostMove = backwardMostMove = 0;
12256           }
12257           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12258           {   int i;
12259               initialRulePlies = FENrulePlies;
12260               for( i=0; i< nrCastlingRights; i++ )
12261                   initialRights[i] = initial_position[CASTLING][i];
12262           }
12263           yyboardindex = forwardMostMove;
12264           free(gameInfo.fen);
12265           gameInfo.fen = NULL;
12266         }
12267
12268         yyboardindex = forwardMostMove;
12269         cm = (ChessMove) Myylex();
12270
12271         /* Handle comments interspersed among the tags */
12272         while (cm == Comment) {
12273             char *p;
12274             if (appData.debugMode)
12275               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12276             p = yy_text;
12277             AppendComment(currentMove, p, FALSE);
12278             yyboardindex = forwardMostMove;
12279             cm = (ChessMove) Myylex();
12280         }
12281     }
12282
12283     /* don't rely on existence of Event tag since if game was
12284      * pasted from clipboard the Event tag may not exist
12285      */
12286     if (numPGNTags > 0){
12287         char *tags;
12288         if (gameInfo.variant == VariantNormal) {
12289           VariantClass v = StringToVariant(gameInfo.event);
12290           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12291           if(v < VariantShogi) gameInfo.variant = v;
12292         }
12293         if (!matchMode) {
12294           if( appData.autoDisplayTags ) {
12295             tags = PGNTags(&gameInfo);
12296             TagsPopUp(tags, CmailMsg());
12297             free(tags);
12298           }
12299         }
12300     } else {
12301         /* Make something up, but don't display it now */
12302         SetGameInfo();
12303         TagsPopDown();
12304     }
12305
12306     if (cm == PositionDiagram) {
12307         int i, j;
12308         char *p;
12309         Board initial_position;
12310
12311         if (appData.debugMode)
12312           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12313
12314         if (!startedFromSetupPosition) {
12315             p = yy_text;
12316             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12317               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12318                 switch (*p) {
12319                   case '{':
12320                   case '[':
12321                   case '-':
12322                   case ' ':
12323                   case '\t':
12324                   case '\n':
12325                   case '\r':
12326                     break;
12327                   default:
12328                     initial_position[i][j++] = CharToPiece(*p);
12329                     break;
12330                 }
12331             while (*p == ' ' || *p == '\t' ||
12332                    *p == '\n' || *p == '\r') p++;
12333
12334             if (strncmp(p, "black", strlen("black"))==0)
12335               blackPlaysFirst = TRUE;
12336             else
12337               blackPlaysFirst = FALSE;
12338             startedFromSetupPosition = TRUE;
12339
12340             CopyBoard(boards[0], initial_position);
12341             if (blackPlaysFirst) {
12342                 currentMove = forwardMostMove = backwardMostMove = 1;
12343                 CopyBoard(boards[1], initial_position);
12344                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12345                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12346                 timeRemaining[0][1] = whiteTimeRemaining;
12347                 timeRemaining[1][1] = blackTimeRemaining;
12348                 if (commentList[0] != NULL) {
12349                     commentList[1] = commentList[0];
12350                     commentList[0] = NULL;
12351                 }
12352             } else {
12353                 currentMove = forwardMostMove = backwardMostMove = 0;
12354             }
12355         }
12356         yyboardindex = forwardMostMove;
12357         cm = (ChessMove) Myylex();
12358     }
12359
12360   if(!creatingBook) {
12361     if (first.pr == NoProc) {
12362         StartChessProgram(&first);
12363     }
12364     InitChessProgram(&first, FALSE);
12365     SendToProgram("force\n", &first);
12366     if (startedFromSetupPosition) {
12367         SendBoard(&first, forwardMostMove);
12368     if (appData.debugMode) {
12369         fprintf(debugFP, "Load Game\n");
12370     }
12371         DisplayBothClocks();
12372     }
12373   }
12374
12375     /* [HGM] server: flag to write setup moves in broadcast file as one */
12376     loadFlag = appData.suppressLoadMoves;
12377
12378     while (cm == Comment) {
12379         char *p;
12380         if (appData.debugMode)
12381           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12382         p = yy_text;
12383         AppendComment(currentMove, p, FALSE);
12384         yyboardindex = forwardMostMove;
12385         cm = (ChessMove) Myylex();
12386     }
12387
12388     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12389         cm == WhiteWins || cm == BlackWins ||
12390         cm == GameIsDrawn || cm == GameUnfinished) {
12391         DisplayMessage("", _("No moves in game"));
12392         if (cmailMsgLoaded) {
12393             if (appData.debugMode)
12394               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12395             ClearHighlights();
12396             flipView = FALSE;
12397         }
12398         DrawPosition(FALSE, boards[currentMove]);
12399         DisplayBothClocks();
12400         gameMode = EditGame;
12401         ModeHighlight();
12402         gameFileFP = NULL;
12403         cmailOldMove = 0;
12404         return TRUE;
12405     }
12406
12407     // [HGM] PV info: routine tests if comment empty
12408     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12409         DisplayComment(currentMove - 1, commentList[currentMove]);
12410     }
12411     if (!matchMode && appData.timeDelay != 0)
12412       DrawPosition(FALSE, boards[currentMove]);
12413
12414     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12415       programStats.ok_to_send = 1;
12416     }
12417
12418     /* if the first token after the PGN tags is a move
12419      * and not move number 1, retrieve it from the parser
12420      */
12421     if (cm != MoveNumberOne)
12422         LoadGameOneMove(cm);
12423
12424     /* load the remaining moves from the file */
12425     while (LoadGameOneMove(EndOfFile)) {
12426       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12427       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12428     }
12429
12430     /* rewind to the start of the game */
12431     currentMove = backwardMostMove;
12432
12433     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12434
12435     if (oldGameMode == AnalyzeFile ||
12436         oldGameMode == AnalyzeMode) {
12437       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12438       AnalyzeFileEvent();
12439     }
12440
12441     if(creatingBook) return TRUE;
12442     if (!matchMode && pos > 0) {
12443         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12444     } else
12445     if (matchMode || appData.timeDelay == 0) {
12446       ToEndEvent();
12447     } else if (appData.timeDelay > 0) {
12448       AutoPlayGameLoop();
12449     }
12450
12451     if (appData.debugMode)
12452         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12453
12454     loadFlag = 0; /* [HGM] true game starts */
12455     return TRUE;
12456 }
12457
12458 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12459 int
12460 ReloadPosition (int offset)
12461 {
12462     int positionNumber = lastLoadPositionNumber + offset;
12463     if (lastLoadPositionFP == NULL) {
12464         DisplayError(_("No position has been loaded yet"), 0);
12465         return FALSE;
12466     }
12467     if (positionNumber <= 0) {
12468         DisplayError(_("Can't back up any further"), 0);
12469         return FALSE;
12470     }
12471     return LoadPosition(lastLoadPositionFP, positionNumber,
12472                         lastLoadPositionTitle);
12473 }
12474
12475 /* Load the nth position from the given file */
12476 int
12477 LoadPositionFromFile (char *filename, int n, char *title)
12478 {
12479     FILE *f;
12480     char buf[MSG_SIZ];
12481
12482     if (strcmp(filename, "-") == 0) {
12483         return LoadPosition(stdin, n, "stdin");
12484     } else {
12485         f = fopen(filename, "rb");
12486         if (f == NULL) {
12487             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12488             DisplayError(buf, errno);
12489             return FALSE;
12490         } else {
12491             return LoadPosition(f, n, title);
12492         }
12493     }
12494 }
12495
12496 /* Load the nth position from the given open file, and close it */
12497 int
12498 LoadPosition (FILE *f, int positionNumber, char *title)
12499 {
12500     char *p, line[MSG_SIZ];
12501     Board initial_position;
12502     int i, j, fenMode, pn;
12503
12504     if (gameMode == Training )
12505         SetTrainingModeOff();
12506
12507     if (gameMode != BeginningOfGame) {
12508         Reset(FALSE, TRUE);
12509     }
12510     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12511         fclose(lastLoadPositionFP);
12512     }
12513     if (positionNumber == 0) positionNumber = 1;
12514     lastLoadPositionFP = f;
12515     lastLoadPositionNumber = positionNumber;
12516     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12517     if (first.pr == NoProc && !appData.noChessProgram) {
12518       StartChessProgram(&first);
12519       InitChessProgram(&first, FALSE);
12520     }
12521     pn = positionNumber;
12522     if (positionNumber < 0) {
12523         /* Negative position number means to seek to that byte offset */
12524         if (fseek(f, -positionNumber, 0) == -1) {
12525             DisplayError(_("Can't seek on position file"), 0);
12526             return FALSE;
12527         };
12528         pn = 1;
12529     } else {
12530         if (fseek(f, 0, 0) == -1) {
12531             if (f == lastLoadPositionFP ?
12532                 positionNumber == lastLoadPositionNumber + 1 :
12533                 positionNumber == 1) {
12534                 pn = 1;
12535             } else {
12536                 DisplayError(_("Can't seek on position file"), 0);
12537                 return FALSE;
12538             }
12539         }
12540     }
12541     /* See if this file is FEN or old-style xboard */
12542     if (fgets(line, MSG_SIZ, f) == NULL) {
12543         DisplayError(_("Position not found in file"), 0);
12544         return FALSE;
12545     }
12546     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12547     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12548
12549     if (pn >= 2) {
12550         if (fenMode || line[0] == '#') pn--;
12551         while (pn > 0) {
12552             /* skip positions before number pn */
12553             if (fgets(line, MSG_SIZ, f) == NULL) {
12554                 Reset(TRUE, TRUE);
12555                 DisplayError(_("Position not found in file"), 0);
12556                 return FALSE;
12557             }
12558             if (fenMode || line[0] == '#') pn--;
12559         }
12560     }
12561
12562     if (fenMode) {
12563         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12564             DisplayError(_("Bad FEN position in file"), 0);
12565             return FALSE;
12566         }
12567     } else {
12568         (void) fgets(line, MSG_SIZ, f);
12569         (void) fgets(line, MSG_SIZ, f);
12570
12571         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12572             (void) fgets(line, MSG_SIZ, f);
12573             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12574                 if (*p == ' ')
12575                   continue;
12576                 initial_position[i][j++] = CharToPiece(*p);
12577             }
12578         }
12579
12580         blackPlaysFirst = FALSE;
12581         if (!feof(f)) {
12582             (void) fgets(line, MSG_SIZ, f);
12583             if (strncmp(line, "black", strlen("black"))==0)
12584               blackPlaysFirst = TRUE;
12585         }
12586     }
12587     startedFromSetupPosition = TRUE;
12588
12589     CopyBoard(boards[0], initial_position);
12590     if (blackPlaysFirst) {
12591         currentMove = forwardMostMove = backwardMostMove = 1;
12592         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12593         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12594         CopyBoard(boards[1], initial_position);
12595         DisplayMessage("", _("Black to play"));
12596     } else {
12597         currentMove = forwardMostMove = backwardMostMove = 0;
12598         DisplayMessage("", _("White to play"));
12599     }
12600     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12601     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12602         SendToProgram("force\n", &first);
12603         SendBoard(&first, forwardMostMove);
12604     }
12605     if (appData.debugMode) {
12606 int i, j;
12607   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12608   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12609         fprintf(debugFP, "Load Position\n");
12610     }
12611
12612     if (positionNumber > 1) {
12613       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12614         DisplayTitle(line);
12615     } else {
12616         DisplayTitle(title);
12617     }
12618     gameMode = EditGame;
12619     ModeHighlight();
12620     ResetClocks();
12621     timeRemaining[0][1] = whiteTimeRemaining;
12622     timeRemaining[1][1] = blackTimeRemaining;
12623     DrawPosition(FALSE, boards[currentMove]);
12624
12625     return TRUE;
12626 }
12627
12628
12629 void
12630 CopyPlayerNameIntoFileName (char **dest, char *src)
12631 {
12632     while (*src != NULLCHAR && *src != ',') {
12633         if (*src == ' ') {
12634             *(*dest)++ = '_';
12635             src++;
12636         } else {
12637             *(*dest)++ = *src++;
12638         }
12639     }
12640 }
12641
12642 char *
12643 DefaultFileName (char *ext)
12644 {
12645     static char def[MSG_SIZ];
12646     char *p;
12647
12648     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12649         p = def;
12650         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12651         *p++ = '-';
12652         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12653         *p++ = '.';
12654         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12655     } else {
12656         def[0] = NULLCHAR;
12657     }
12658     return def;
12659 }
12660
12661 /* Save the current game to the given file */
12662 int
12663 SaveGameToFile (char *filename, int append)
12664 {
12665     FILE *f;
12666     char buf[MSG_SIZ];
12667     int result, i, t,tot=0;
12668
12669     if (strcmp(filename, "-") == 0) {
12670         return SaveGame(stdout, 0, NULL);
12671     } else {
12672         for(i=0; i<10; i++) { // upto 10 tries
12673              f = fopen(filename, append ? "a" : "w");
12674              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12675              if(f || errno != 13) break;
12676              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12677              tot += t;
12678         }
12679         if (f == NULL) {
12680             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12681             DisplayError(buf, errno);
12682             return FALSE;
12683         } else {
12684             safeStrCpy(buf, lastMsg, MSG_SIZ);
12685             DisplayMessage(_("Waiting for access to save file"), "");
12686             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12687             DisplayMessage(_("Saving game"), "");
12688             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12689             result = SaveGame(f, 0, NULL);
12690             DisplayMessage(buf, "");
12691             return result;
12692         }
12693     }
12694 }
12695
12696 char *
12697 SavePart (char *str)
12698 {
12699     static char buf[MSG_SIZ];
12700     char *p;
12701
12702     p = strchr(str, ' ');
12703     if (p == NULL) return str;
12704     strncpy(buf, str, p - str);
12705     buf[p - str] = NULLCHAR;
12706     return buf;
12707 }
12708
12709 #define PGN_MAX_LINE 75
12710
12711 #define PGN_SIDE_WHITE  0
12712 #define PGN_SIDE_BLACK  1
12713
12714 static int
12715 FindFirstMoveOutOfBook (int side)
12716 {
12717     int result = -1;
12718
12719     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12720         int index = backwardMostMove;
12721         int has_book_hit = 0;
12722
12723         if( (index % 2) != side ) {
12724             index++;
12725         }
12726
12727         while( index < forwardMostMove ) {
12728             /* Check to see if engine is in book */
12729             int depth = pvInfoList[index].depth;
12730             int score = pvInfoList[index].score;
12731             int in_book = 0;
12732
12733             if( depth <= 2 ) {
12734                 in_book = 1;
12735             }
12736             else if( score == 0 && depth == 63 ) {
12737                 in_book = 1; /* Zappa */
12738             }
12739             else if( score == 2 && depth == 99 ) {
12740                 in_book = 1; /* Abrok */
12741             }
12742
12743             has_book_hit += in_book;
12744
12745             if( ! in_book ) {
12746                 result = index;
12747
12748                 break;
12749             }
12750
12751             index += 2;
12752         }
12753     }
12754
12755     return result;
12756 }
12757
12758 void
12759 GetOutOfBookInfo (char * buf)
12760 {
12761     int oob[2];
12762     int i;
12763     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12764
12765     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12766     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12767
12768     *buf = '\0';
12769
12770     if( oob[0] >= 0 || oob[1] >= 0 ) {
12771         for( i=0; i<2; i++ ) {
12772             int idx = oob[i];
12773
12774             if( idx >= 0 ) {
12775                 if( i > 0 && oob[0] >= 0 ) {
12776                     strcat( buf, "   " );
12777                 }
12778
12779                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12780                 sprintf( buf+strlen(buf), "%s%.2f",
12781                     pvInfoList[idx].score >= 0 ? "+" : "",
12782                     pvInfoList[idx].score / 100.0 );
12783             }
12784         }
12785     }
12786 }
12787
12788 /* Save game in PGN style and close the file */
12789 int
12790 SaveGamePGN (FILE *f)
12791 {
12792     int i, offset, linelen, newblock;
12793 //    char *movetext;
12794     char numtext[32];
12795     int movelen, numlen, blank;
12796     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12797
12798     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12799
12800     PrintPGNTags(f, &gameInfo);
12801
12802     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12803
12804     if (backwardMostMove > 0 || startedFromSetupPosition) {
12805         char *fen = PositionToFEN(backwardMostMove, NULL);
12806         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12807         fprintf(f, "\n{--------------\n");
12808         PrintPosition(f, backwardMostMove);
12809         fprintf(f, "--------------}\n");
12810         free(fen);
12811     }
12812     else {
12813         /* [AS] Out of book annotation */
12814         if( appData.saveOutOfBookInfo ) {
12815             char buf[64];
12816
12817             GetOutOfBookInfo( buf );
12818
12819             if( buf[0] != '\0' ) {
12820                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12821             }
12822         }
12823
12824         fprintf(f, "\n");
12825     }
12826
12827     i = backwardMostMove;
12828     linelen = 0;
12829     newblock = TRUE;
12830
12831     while (i < forwardMostMove) {
12832         /* Print comments preceding this move */
12833         if (commentList[i] != NULL) {
12834             if (linelen > 0) fprintf(f, "\n");
12835             fprintf(f, "%s", commentList[i]);
12836             linelen = 0;
12837             newblock = TRUE;
12838         }
12839
12840         /* Format move number */
12841         if ((i % 2) == 0)
12842           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12843         else
12844           if (newblock)
12845             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12846           else
12847             numtext[0] = NULLCHAR;
12848
12849         numlen = strlen(numtext);
12850         newblock = FALSE;
12851
12852         /* Print move number */
12853         blank = linelen > 0 && numlen > 0;
12854         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12855             fprintf(f, "\n");
12856             linelen = 0;
12857             blank = 0;
12858         }
12859         if (blank) {
12860             fprintf(f, " ");
12861             linelen++;
12862         }
12863         fprintf(f, "%s", numtext);
12864         linelen += numlen;
12865
12866         /* Get move */
12867         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12868         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12869
12870         /* Print move */
12871         blank = linelen > 0 && movelen > 0;
12872         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12873             fprintf(f, "\n");
12874             linelen = 0;
12875             blank = 0;
12876         }
12877         if (blank) {
12878             fprintf(f, " ");
12879             linelen++;
12880         }
12881         fprintf(f, "%s", move_buffer);
12882         linelen += movelen;
12883
12884         /* [AS] Add PV info if present */
12885         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12886             /* [HGM] add time */
12887             char buf[MSG_SIZ]; int seconds;
12888
12889             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12890
12891             if( seconds <= 0)
12892               buf[0] = 0;
12893             else
12894               if( seconds < 30 )
12895                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12896               else
12897                 {
12898                   seconds = (seconds + 4)/10; // round to full seconds
12899                   if( seconds < 60 )
12900                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12901                   else
12902                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12903                 }
12904
12905             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12906                       pvInfoList[i].score >= 0 ? "+" : "",
12907                       pvInfoList[i].score / 100.0,
12908                       pvInfoList[i].depth,
12909                       buf );
12910
12911             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12912
12913             /* Print score/depth */
12914             blank = linelen > 0 && movelen > 0;
12915             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12916                 fprintf(f, "\n");
12917                 linelen = 0;
12918                 blank = 0;
12919             }
12920             if (blank) {
12921                 fprintf(f, " ");
12922                 linelen++;
12923             }
12924             fprintf(f, "%s", move_buffer);
12925             linelen += movelen;
12926         }
12927
12928         i++;
12929     }
12930
12931     /* Start a new line */
12932     if (linelen > 0) fprintf(f, "\n");
12933
12934     /* Print comments after last move */
12935     if (commentList[i] != NULL) {
12936         fprintf(f, "%s\n", commentList[i]);
12937     }
12938
12939     /* Print result */
12940     if (gameInfo.resultDetails != NULL &&
12941         gameInfo.resultDetails[0] != NULLCHAR) {
12942         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12943                 PGNResult(gameInfo.result));
12944     } else {
12945         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12946     }
12947
12948     fclose(f);
12949     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12950     return TRUE;
12951 }
12952
12953 /* Save game in old style and close the file */
12954 int
12955 SaveGameOldStyle (FILE *f)
12956 {
12957     int i, offset;
12958     time_t tm;
12959
12960     tm = time((time_t *) NULL);
12961
12962     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12963     PrintOpponents(f);
12964
12965     if (backwardMostMove > 0 || startedFromSetupPosition) {
12966         fprintf(f, "\n[--------------\n");
12967         PrintPosition(f, backwardMostMove);
12968         fprintf(f, "--------------]\n");
12969     } else {
12970         fprintf(f, "\n");
12971     }
12972
12973     i = backwardMostMove;
12974     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12975
12976     while (i < forwardMostMove) {
12977         if (commentList[i] != NULL) {
12978             fprintf(f, "[%s]\n", commentList[i]);
12979         }
12980
12981         if ((i % 2) == 1) {
12982             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12983             i++;
12984         } else {
12985             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12986             i++;
12987             if (commentList[i] != NULL) {
12988                 fprintf(f, "\n");
12989                 continue;
12990             }
12991             if (i >= forwardMostMove) {
12992                 fprintf(f, "\n");
12993                 break;
12994             }
12995             fprintf(f, "%s\n", parseList[i]);
12996             i++;
12997         }
12998     }
12999
13000     if (commentList[i] != NULL) {
13001         fprintf(f, "[%s]\n", commentList[i]);
13002     }
13003
13004     /* This isn't really the old style, but it's close enough */
13005     if (gameInfo.resultDetails != NULL &&
13006         gameInfo.resultDetails[0] != NULLCHAR) {
13007         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13008                 gameInfo.resultDetails);
13009     } else {
13010         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13011     }
13012
13013     fclose(f);
13014     return TRUE;
13015 }
13016
13017 /* Save the current game to open file f and close the file */
13018 int
13019 SaveGame (FILE *f, int dummy, char *dummy2)
13020 {
13021     if (gameMode == EditPosition) EditPositionDone(TRUE);
13022     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13023     if (appData.oldSaveStyle)
13024       return SaveGameOldStyle(f);
13025     else
13026       return SaveGamePGN(f);
13027 }
13028
13029 /* Save the current position to the given file */
13030 int
13031 SavePositionToFile (char *filename)
13032 {
13033     FILE *f;
13034     char buf[MSG_SIZ];
13035
13036     if (strcmp(filename, "-") == 0) {
13037         return SavePosition(stdout, 0, NULL);
13038     } else {
13039         f = fopen(filename, "a");
13040         if (f == NULL) {
13041             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13042             DisplayError(buf, errno);
13043             return FALSE;
13044         } else {
13045             safeStrCpy(buf, lastMsg, MSG_SIZ);
13046             DisplayMessage(_("Waiting for access to save file"), "");
13047             flock(fileno(f), LOCK_EX); // [HGM] lock
13048             DisplayMessage(_("Saving position"), "");
13049             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13050             SavePosition(f, 0, NULL);
13051             DisplayMessage(buf, "");
13052             return TRUE;
13053         }
13054     }
13055 }
13056
13057 /* Save the current position to the given open file and close the file */
13058 int
13059 SavePosition (FILE *f, int dummy, char *dummy2)
13060 {
13061     time_t tm;
13062     char *fen;
13063
13064     if (gameMode == EditPosition) EditPositionDone(TRUE);
13065     if (appData.oldSaveStyle) {
13066         tm = time((time_t *) NULL);
13067
13068         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13069         PrintOpponents(f);
13070         fprintf(f, "[--------------\n");
13071         PrintPosition(f, currentMove);
13072         fprintf(f, "--------------]\n");
13073     } else {
13074         fen = PositionToFEN(currentMove, NULL);
13075         fprintf(f, "%s\n", fen);
13076         free(fen);
13077     }
13078     fclose(f);
13079     return TRUE;
13080 }
13081
13082 void
13083 ReloadCmailMsgEvent (int unregister)
13084 {
13085 #if !WIN32
13086     static char *inFilename = NULL;
13087     static char *outFilename;
13088     int i;
13089     struct stat inbuf, outbuf;
13090     int status;
13091
13092     /* Any registered moves are unregistered if unregister is set, */
13093     /* i.e. invoked by the signal handler */
13094     if (unregister) {
13095         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13096             cmailMoveRegistered[i] = FALSE;
13097             if (cmailCommentList[i] != NULL) {
13098                 free(cmailCommentList[i]);
13099                 cmailCommentList[i] = NULL;
13100             }
13101         }
13102         nCmailMovesRegistered = 0;
13103     }
13104
13105     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13106         cmailResult[i] = CMAIL_NOT_RESULT;
13107     }
13108     nCmailResults = 0;
13109
13110     if (inFilename == NULL) {
13111         /* Because the filenames are static they only get malloced once  */
13112         /* and they never get freed                                      */
13113         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13114         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13115
13116         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13117         sprintf(outFilename, "%s.out", appData.cmailGameName);
13118     }
13119
13120     status = stat(outFilename, &outbuf);
13121     if (status < 0) {
13122         cmailMailedMove = FALSE;
13123     } else {
13124         status = stat(inFilename, &inbuf);
13125         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13126     }
13127
13128     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13129        counts the games, notes how each one terminated, etc.
13130
13131        It would be nice to remove this kludge and instead gather all
13132        the information while building the game list.  (And to keep it
13133        in the game list nodes instead of having a bunch of fixed-size
13134        parallel arrays.)  Note this will require getting each game's
13135        termination from the PGN tags, as the game list builder does
13136        not process the game moves.  --mann
13137        */
13138     cmailMsgLoaded = TRUE;
13139     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13140
13141     /* Load first game in the file or popup game menu */
13142     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13143
13144 #endif /* !WIN32 */
13145     return;
13146 }
13147
13148 int
13149 RegisterMove ()
13150 {
13151     FILE *f;
13152     char string[MSG_SIZ];
13153
13154     if (   cmailMailedMove
13155         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13156         return TRUE;            /* Allow free viewing  */
13157     }
13158
13159     /* Unregister move to ensure that we don't leave RegisterMove        */
13160     /* with the move registered when the conditions for registering no   */
13161     /* longer hold                                                       */
13162     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13163         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13164         nCmailMovesRegistered --;
13165
13166         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13167           {
13168               free(cmailCommentList[lastLoadGameNumber - 1]);
13169               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13170           }
13171     }
13172
13173     if (cmailOldMove == -1) {
13174         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13175         return FALSE;
13176     }
13177
13178     if (currentMove > cmailOldMove + 1) {
13179         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13180         return FALSE;
13181     }
13182
13183     if (currentMove < cmailOldMove) {
13184         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13185         return FALSE;
13186     }
13187
13188     if (forwardMostMove > currentMove) {
13189         /* Silently truncate extra moves */
13190         TruncateGame();
13191     }
13192
13193     if (   (currentMove == cmailOldMove + 1)
13194         || (   (currentMove == cmailOldMove)
13195             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13196                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13197         if (gameInfo.result != GameUnfinished) {
13198             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13199         }
13200
13201         if (commentList[currentMove] != NULL) {
13202             cmailCommentList[lastLoadGameNumber - 1]
13203               = StrSave(commentList[currentMove]);
13204         }
13205         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13206
13207         if (appData.debugMode)
13208           fprintf(debugFP, "Saving %s for game %d\n",
13209                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13210
13211         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13212
13213         f = fopen(string, "w");
13214         if (appData.oldSaveStyle) {
13215             SaveGameOldStyle(f); /* also closes the file */
13216
13217             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13218             f = fopen(string, "w");
13219             SavePosition(f, 0, NULL); /* also closes the file */
13220         } else {
13221             fprintf(f, "{--------------\n");
13222             PrintPosition(f, currentMove);
13223             fprintf(f, "--------------}\n\n");
13224
13225             SaveGame(f, 0, NULL); /* also closes the file*/
13226         }
13227
13228         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13229         nCmailMovesRegistered ++;
13230     } else if (nCmailGames == 1) {
13231         DisplayError(_("You have not made a move yet"), 0);
13232         return FALSE;
13233     }
13234
13235     return TRUE;
13236 }
13237
13238 void
13239 MailMoveEvent ()
13240 {
13241 #if !WIN32
13242     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13243     FILE *commandOutput;
13244     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13245     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13246     int nBuffers;
13247     int i;
13248     int archived;
13249     char *arcDir;
13250
13251     if (! cmailMsgLoaded) {
13252         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13253         return;
13254     }
13255
13256     if (nCmailGames == nCmailResults) {
13257         DisplayError(_("No unfinished games"), 0);
13258         return;
13259     }
13260
13261 #if CMAIL_PROHIBIT_REMAIL
13262     if (cmailMailedMove) {
13263       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);
13264         DisplayError(msg, 0);
13265         return;
13266     }
13267 #endif
13268
13269     if (! (cmailMailedMove || RegisterMove())) return;
13270
13271     if (   cmailMailedMove
13272         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13273       snprintf(string, MSG_SIZ, partCommandString,
13274                appData.debugMode ? " -v" : "", appData.cmailGameName);
13275         commandOutput = popen(string, "r");
13276
13277         if (commandOutput == NULL) {
13278             DisplayError(_("Failed to invoke cmail"), 0);
13279         } else {
13280             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13281                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13282             }
13283             if (nBuffers > 1) {
13284                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13285                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13286                 nBytes = MSG_SIZ - 1;
13287             } else {
13288                 (void) memcpy(msg, buffer, nBytes);
13289             }
13290             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13291
13292             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13293                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13294
13295                 archived = TRUE;
13296                 for (i = 0; i < nCmailGames; i ++) {
13297                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13298                         archived = FALSE;
13299                     }
13300                 }
13301                 if (   archived
13302                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13303                         != NULL)) {
13304                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13305                            arcDir,
13306                            appData.cmailGameName,
13307                            gameInfo.date);
13308                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13309                     cmailMsgLoaded = FALSE;
13310                 }
13311             }
13312
13313             DisplayInformation(msg);
13314             pclose(commandOutput);
13315         }
13316     } else {
13317         if ((*cmailMsg) != '\0') {
13318             DisplayInformation(cmailMsg);
13319         }
13320     }
13321
13322     return;
13323 #endif /* !WIN32 */
13324 }
13325
13326 char *
13327 CmailMsg ()
13328 {
13329 #if WIN32
13330     return NULL;
13331 #else
13332     int  prependComma = 0;
13333     char number[5];
13334     char string[MSG_SIZ];       /* Space for game-list */
13335     int  i;
13336
13337     if (!cmailMsgLoaded) return "";
13338
13339     if (cmailMailedMove) {
13340       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13341     } else {
13342         /* Create a list of games left */
13343       snprintf(string, MSG_SIZ, "[");
13344         for (i = 0; i < nCmailGames; i ++) {
13345             if (! (   cmailMoveRegistered[i]
13346                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13347                 if (prependComma) {
13348                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13349                 } else {
13350                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13351                     prependComma = 1;
13352                 }
13353
13354                 strcat(string, number);
13355             }
13356         }
13357         strcat(string, "]");
13358
13359         if (nCmailMovesRegistered + nCmailResults == 0) {
13360             switch (nCmailGames) {
13361               case 1:
13362                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13363                 break;
13364
13365               case 2:
13366                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13367                 break;
13368
13369               default:
13370                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13371                          nCmailGames);
13372                 break;
13373             }
13374         } else {
13375             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13376               case 1:
13377                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13378                          string);
13379                 break;
13380
13381               case 0:
13382                 if (nCmailResults == nCmailGames) {
13383                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13384                 } else {
13385                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13386                 }
13387                 break;
13388
13389               default:
13390                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13391                          string);
13392             }
13393         }
13394     }
13395     return cmailMsg;
13396 #endif /* WIN32 */
13397 }
13398
13399 void
13400 ResetGameEvent ()
13401 {
13402     if (gameMode == Training)
13403       SetTrainingModeOff();
13404
13405     Reset(TRUE, TRUE);
13406     cmailMsgLoaded = FALSE;
13407     if (appData.icsActive) {
13408       SendToICS(ics_prefix);
13409       SendToICS("refresh\n");
13410     }
13411 }
13412
13413 void
13414 ExitEvent (int status)
13415 {
13416     exiting++;
13417     if (exiting > 2) {
13418       /* Give up on clean exit */
13419       exit(status);
13420     }
13421     if (exiting > 1) {
13422       /* Keep trying for clean exit */
13423       return;
13424     }
13425
13426     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13427
13428     if (telnetISR != NULL) {
13429       RemoveInputSource(telnetISR);
13430     }
13431     if (icsPR != NoProc) {
13432       DestroyChildProcess(icsPR, TRUE);
13433     }
13434
13435     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13436     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13437
13438     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13439     /* make sure this other one finishes before killing it!                  */
13440     if(endingGame) { int count = 0;
13441         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13442         while(endingGame && count++ < 10) DoSleep(1);
13443         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13444     }
13445
13446     /* Kill off chess programs */
13447     if (first.pr != NoProc) {
13448         ExitAnalyzeMode();
13449
13450         DoSleep( appData.delayBeforeQuit );
13451         SendToProgram("quit\n", &first);
13452         DoSleep( appData.delayAfterQuit );
13453         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13454     }
13455     if (second.pr != NoProc) {
13456         DoSleep( appData.delayBeforeQuit );
13457         SendToProgram("quit\n", &second);
13458         DoSleep( appData.delayAfterQuit );
13459         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13460     }
13461     if (first.isr != NULL) {
13462         RemoveInputSource(first.isr);
13463     }
13464     if (second.isr != NULL) {
13465         RemoveInputSource(second.isr);
13466     }
13467
13468     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13469     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13470
13471     ShutDownFrontEnd();
13472     exit(status);
13473 }
13474
13475 void
13476 PauseEngine (ChessProgramState *cps)
13477 {
13478     SendToProgram("pause\n", cps);
13479     cps->pause = 2;
13480 }
13481
13482 void
13483 UnPauseEngine (ChessProgramState *cps)
13484 {
13485     SendToProgram("resume\n", cps);
13486     cps->pause = 1;
13487 }
13488
13489 void
13490 PauseEvent ()
13491 {
13492     if (appData.debugMode)
13493         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13494     if (pausing) {
13495         pausing = FALSE;
13496         ModeHighlight();
13497         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13498             StartClocks();
13499             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13500                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13501                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13502             }
13503             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13504             HandleMachineMove(stashedInputMove, stalledEngine);
13505             stalledEngine = NULL;
13506             return;
13507         }
13508         if (gameMode == MachinePlaysWhite ||
13509             gameMode == TwoMachinesPlay   ||
13510             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13511             if(first.pause)  UnPauseEngine(&first);
13512             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13513             if(second.pause) UnPauseEngine(&second);
13514             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13515             StartClocks();
13516         } else {
13517             DisplayBothClocks();
13518         }
13519         if (gameMode == PlayFromGameFile) {
13520             if (appData.timeDelay >= 0)
13521                 AutoPlayGameLoop();
13522         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13523             Reset(FALSE, TRUE);
13524             SendToICS(ics_prefix);
13525             SendToICS("refresh\n");
13526         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13527             ForwardInner(forwardMostMove);
13528         }
13529         pauseExamInvalid = FALSE;
13530     } else {
13531         switch (gameMode) {
13532           default:
13533             return;
13534           case IcsExamining:
13535             pauseExamForwardMostMove = forwardMostMove;
13536             pauseExamInvalid = FALSE;
13537             /* fall through */
13538           case IcsObserving:
13539           case IcsPlayingWhite:
13540           case IcsPlayingBlack:
13541             pausing = TRUE;
13542             ModeHighlight();
13543             return;
13544           case PlayFromGameFile:
13545             (void) StopLoadGameTimer();
13546             pausing = TRUE;
13547             ModeHighlight();
13548             break;
13549           case BeginningOfGame:
13550             if (appData.icsActive) return;
13551             /* else fall through */
13552           case MachinePlaysWhite:
13553           case MachinePlaysBlack:
13554           case TwoMachinesPlay:
13555             if (forwardMostMove == 0)
13556               return;           /* don't pause if no one has moved */
13557             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13558                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13559                 if(onMove->pause) {           // thinking engine can be paused
13560                     PauseEngine(onMove);      // do it
13561                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13562                         PauseEngine(onMove->other);
13563                     else
13564                         SendToProgram("easy\n", onMove->other);
13565                     StopClocks();
13566                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13567             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13568                 if(first.pause) {
13569                     PauseEngine(&first);
13570                     StopClocks();
13571                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13572             } else { // human on move, pause pondering by either method
13573                 if(first.pause)
13574                     PauseEngine(&first);
13575                 else if(appData.ponderNextMove)
13576                     SendToProgram("easy\n", &first);
13577                 StopClocks();
13578             }
13579             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13580           case AnalyzeMode:
13581             pausing = TRUE;
13582             ModeHighlight();
13583             break;
13584         }
13585     }
13586 }
13587
13588 void
13589 EditCommentEvent ()
13590 {
13591     char title[MSG_SIZ];
13592
13593     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13594       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13595     } else {
13596       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13597                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13598                parseList[currentMove - 1]);
13599     }
13600
13601     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13602 }
13603
13604
13605 void
13606 EditTagsEvent ()
13607 {
13608     char *tags = PGNTags(&gameInfo);
13609     bookUp = FALSE;
13610     EditTagsPopUp(tags, NULL);
13611     free(tags);
13612 }
13613
13614 void
13615 ToggleSecond ()
13616 {
13617   if(second.analyzing) {
13618     SendToProgram("exit\n", &second);
13619     second.analyzing = FALSE;
13620   } else {
13621     if (second.pr == NoProc) StartChessProgram(&second);
13622     InitChessProgram(&second, FALSE);
13623     FeedMovesToProgram(&second, currentMove);
13624
13625     SendToProgram("analyze\n", &second);
13626     second.analyzing = TRUE;
13627   }
13628 }
13629
13630 /* Toggle ShowThinking */
13631 void
13632 ToggleShowThinking()
13633 {
13634   appData.showThinking = !appData.showThinking;
13635   ShowThinkingEvent();
13636 }
13637
13638 int
13639 AnalyzeModeEvent ()
13640 {
13641     char buf[MSG_SIZ];
13642
13643     if (!first.analysisSupport) {
13644       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13645       DisplayError(buf, 0);
13646       return 0;
13647     }
13648     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13649     if (appData.icsActive) {
13650         if (gameMode != IcsObserving) {
13651           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13652             DisplayError(buf, 0);
13653             /* secure check */
13654             if (appData.icsEngineAnalyze) {
13655                 if (appData.debugMode)
13656                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13657                 ExitAnalyzeMode();
13658                 ModeHighlight();
13659             }
13660             return 0;
13661         }
13662         /* if enable, user wants to disable icsEngineAnalyze */
13663         if (appData.icsEngineAnalyze) {
13664                 ExitAnalyzeMode();
13665                 ModeHighlight();
13666                 return 0;
13667         }
13668         appData.icsEngineAnalyze = TRUE;
13669         if (appData.debugMode)
13670             fprintf(debugFP, "ICS engine analyze starting... \n");
13671     }
13672
13673     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13674     if (appData.noChessProgram || gameMode == AnalyzeMode)
13675       return 0;
13676
13677     if (gameMode != AnalyzeFile) {
13678         if (!appData.icsEngineAnalyze) {
13679                EditGameEvent();
13680                if (gameMode != EditGame) return 0;
13681         }
13682         if (!appData.showThinking) ToggleShowThinking();
13683         ResurrectChessProgram();
13684         SendToProgram("analyze\n", &first);
13685         first.analyzing = TRUE;
13686         /*first.maybeThinking = TRUE;*/
13687         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13688         EngineOutputPopUp();
13689     }
13690     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13691     pausing = FALSE;
13692     ModeHighlight();
13693     SetGameInfo();
13694
13695     StartAnalysisClock();
13696     GetTimeMark(&lastNodeCountTime);
13697     lastNodeCount = 0;
13698     return 1;
13699 }
13700
13701 void
13702 AnalyzeFileEvent ()
13703 {
13704     if (appData.noChessProgram || gameMode == AnalyzeFile)
13705       return;
13706
13707     if (!first.analysisSupport) {
13708       char buf[MSG_SIZ];
13709       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13710       DisplayError(buf, 0);
13711       return;
13712     }
13713
13714     if (gameMode != AnalyzeMode) {
13715         keepInfo = 1; // mere annotating should not alter PGN tags
13716         EditGameEvent();
13717         keepInfo = 0;
13718         if (gameMode != EditGame) return;
13719         if (!appData.showThinking) ToggleShowThinking();
13720         ResurrectChessProgram();
13721         SendToProgram("analyze\n", &first);
13722         first.analyzing = TRUE;
13723         /*first.maybeThinking = TRUE;*/
13724         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13725         EngineOutputPopUp();
13726     }
13727     gameMode = AnalyzeFile;
13728     pausing = FALSE;
13729     ModeHighlight();
13730
13731     StartAnalysisClock();
13732     GetTimeMark(&lastNodeCountTime);
13733     lastNodeCount = 0;
13734     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13735     AnalysisPeriodicEvent(1);
13736 }
13737
13738 void
13739 MachineWhiteEvent ()
13740 {
13741     char buf[MSG_SIZ];
13742     char *bookHit = NULL;
13743
13744     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13745       return;
13746
13747
13748     if (gameMode == PlayFromGameFile ||
13749         gameMode == TwoMachinesPlay  ||
13750         gameMode == Training         ||
13751         gameMode == AnalyzeMode      ||
13752         gameMode == EndOfGame)
13753         EditGameEvent();
13754
13755     if (gameMode == EditPosition)
13756         EditPositionDone(TRUE);
13757
13758     if (!WhiteOnMove(currentMove)) {
13759         DisplayError(_("It is not White's turn"), 0);
13760         return;
13761     }
13762
13763     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13764       ExitAnalyzeMode();
13765
13766     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13767         gameMode == AnalyzeFile)
13768         TruncateGame();
13769
13770     ResurrectChessProgram();    /* in case it isn't running */
13771     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13772         gameMode = MachinePlaysWhite;
13773         ResetClocks();
13774     } else
13775     gameMode = MachinePlaysWhite;
13776     pausing = FALSE;
13777     ModeHighlight();
13778     SetGameInfo();
13779     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13780     DisplayTitle(buf);
13781     if (first.sendName) {
13782       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13783       SendToProgram(buf, &first);
13784     }
13785     if (first.sendTime) {
13786       if (first.useColors) {
13787         SendToProgram("black\n", &first); /*gnu kludge*/
13788       }
13789       SendTimeRemaining(&first, TRUE);
13790     }
13791     if (first.useColors) {
13792       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13793     }
13794     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13795     SetMachineThinkingEnables();
13796     first.maybeThinking = TRUE;
13797     StartClocks();
13798     firstMove = FALSE;
13799
13800     if (appData.autoFlipView && !flipView) {
13801       flipView = !flipView;
13802       DrawPosition(FALSE, NULL);
13803       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13804     }
13805
13806     if(bookHit) { // [HGM] book: simulate book reply
13807         static char bookMove[MSG_SIZ]; // a bit generous?
13808
13809         programStats.nodes = programStats.depth = programStats.time =
13810         programStats.score = programStats.got_only_move = 0;
13811         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13812
13813         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13814         strcat(bookMove, bookHit);
13815         HandleMachineMove(bookMove, &first);
13816     }
13817 }
13818
13819 void
13820 MachineBlackEvent ()
13821 {
13822   char buf[MSG_SIZ];
13823   char *bookHit = NULL;
13824
13825     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13826         return;
13827
13828
13829     if (gameMode == PlayFromGameFile ||
13830         gameMode == TwoMachinesPlay  ||
13831         gameMode == Training         ||
13832         gameMode == AnalyzeMode      ||
13833         gameMode == EndOfGame)
13834         EditGameEvent();
13835
13836     if (gameMode == EditPosition)
13837         EditPositionDone(TRUE);
13838
13839     if (WhiteOnMove(currentMove)) {
13840         DisplayError(_("It is not Black's turn"), 0);
13841         return;
13842     }
13843
13844     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13845       ExitAnalyzeMode();
13846
13847     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13848         gameMode == AnalyzeFile)
13849         TruncateGame();
13850
13851     ResurrectChessProgram();    /* in case it isn't running */
13852     gameMode = MachinePlaysBlack;
13853     pausing = FALSE;
13854     ModeHighlight();
13855     SetGameInfo();
13856     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13857     DisplayTitle(buf);
13858     if (first.sendName) {
13859       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13860       SendToProgram(buf, &first);
13861     }
13862     if (first.sendTime) {
13863       if (first.useColors) {
13864         SendToProgram("white\n", &first); /*gnu kludge*/
13865       }
13866       SendTimeRemaining(&first, FALSE);
13867     }
13868     if (first.useColors) {
13869       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13870     }
13871     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13872     SetMachineThinkingEnables();
13873     first.maybeThinking = TRUE;
13874     StartClocks();
13875
13876     if (appData.autoFlipView && flipView) {
13877       flipView = !flipView;
13878       DrawPosition(FALSE, NULL);
13879       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13880     }
13881     if(bookHit) { // [HGM] book: simulate book reply
13882         static char bookMove[MSG_SIZ]; // a bit generous?
13883
13884         programStats.nodes = programStats.depth = programStats.time =
13885         programStats.score = programStats.got_only_move = 0;
13886         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13887
13888         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13889         strcat(bookMove, bookHit);
13890         HandleMachineMove(bookMove, &first);
13891     }
13892 }
13893
13894
13895 void
13896 DisplayTwoMachinesTitle ()
13897 {
13898     char buf[MSG_SIZ];
13899     if (appData.matchGames > 0) {
13900         if(appData.tourneyFile[0]) {
13901           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13902                    gameInfo.white, _("vs."), gameInfo.black,
13903                    nextGame+1, appData.matchGames+1,
13904                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13905         } else
13906         if (first.twoMachinesColor[0] == 'w') {
13907           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13908                    gameInfo.white, _("vs."),  gameInfo.black,
13909                    first.matchWins, second.matchWins,
13910                    matchGame - 1 - (first.matchWins + second.matchWins));
13911         } else {
13912           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13913                    gameInfo.white, _("vs."), gameInfo.black,
13914                    second.matchWins, first.matchWins,
13915                    matchGame - 1 - (first.matchWins + second.matchWins));
13916         }
13917     } else {
13918       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13919     }
13920     DisplayTitle(buf);
13921 }
13922
13923 void
13924 SettingsMenuIfReady ()
13925 {
13926   if (second.lastPing != second.lastPong) {
13927     DisplayMessage("", _("Waiting for second chess program"));
13928     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13929     return;
13930   }
13931   ThawUI();
13932   DisplayMessage("", "");
13933   SettingsPopUp(&second);
13934 }
13935
13936 int
13937 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13938 {
13939     char buf[MSG_SIZ];
13940     if (cps->pr == NoProc) {
13941         StartChessProgram(cps);
13942         if (cps->protocolVersion == 1) {
13943           retry();
13944           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13945         } else {
13946           /* kludge: allow timeout for initial "feature" command */
13947           if(retry != TwoMachinesEventIfReady) FreezeUI();
13948           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13949           DisplayMessage("", buf);
13950           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13951         }
13952         return 1;
13953     }
13954     return 0;
13955 }
13956
13957 void
13958 TwoMachinesEvent P((void))
13959 {
13960     int i;
13961     char buf[MSG_SIZ];
13962     ChessProgramState *onmove;
13963     char *bookHit = NULL;
13964     static int stalling = 0;
13965     TimeMark now;
13966     long wait;
13967
13968     if (appData.noChessProgram) return;
13969
13970     switch (gameMode) {
13971       case TwoMachinesPlay:
13972         return;
13973       case MachinePlaysWhite:
13974       case MachinePlaysBlack:
13975         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13976             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13977             return;
13978         }
13979         /* fall through */
13980       case BeginningOfGame:
13981       case PlayFromGameFile:
13982       case EndOfGame:
13983         EditGameEvent();
13984         if (gameMode != EditGame) return;
13985         break;
13986       case EditPosition:
13987         EditPositionDone(TRUE);
13988         break;
13989       case AnalyzeMode:
13990       case AnalyzeFile:
13991         ExitAnalyzeMode();
13992         break;
13993       case EditGame:
13994       default:
13995         break;
13996     }
13997
13998 //    forwardMostMove = currentMove;
13999     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14000     startingEngine = TRUE;
14001
14002     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14003
14004     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14005     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14006       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14007       return;
14008     }
14009     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14010
14011     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14012         startingEngine = FALSE;
14013         DisplayError("second engine does not play this", 0);
14014         return;
14015     }
14016
14017     if(!stalling) {
14018       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14019       SendToProgram("force\n", &second);
14020       stalling = 1;
14021       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14022       return;
14023     }
14024     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14025     if(appData.matchPause>10000 || appData.matchPause<10)
14026                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14027     wait = SubtractTimeMarks(&now, &pauseStart);
14028     if(wait < appData.matchPause) {
14029         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14030         return;
14031     }
14032     // we are now committed to starting the game
14033     stalling = 0;
14034     DisplayMessage("", "");
14035     if (startedFromSetupPosition) {
14036         SendBoard(&second, backwardMostMove);
14037     if (appData.debugMode) {
14038         fprintf(debugFP, "Two Machines\n");
14039     }
14040     }
14041     for (i = backwardMostMove; i < forwardMostMove; i++) {
14042         SendMoveToProgram(i, &second);
14043     }
14044
14045     gameMode = TwoMachinesPlay;
14046     pausing = startingEngine = FALSE;
14047     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14048     SetGameInfo();
14049     DisplayTwoMachinesTitle();
14050     firstMove = TRUE;
14051     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14052         onmove = &first;
14053     } else {
14054         onmove = &second;
14055     }
14056     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14057     SendToProgram(first.computerString, &first);
14058     if (first.sendName) {
14059       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14060       SendToProgram(buf, &first);
14061     }
14062     SendToProgram(second.computerString, &second);
14063     if (second.sendName) {
14064       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14065       SendToProgram(buf, &second);
14066     }
14067
14068     ResetClocks();
14069     if (!first.sendTime || !second.sendTime) {
14070         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14071         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14072     }
14073     if (onmove->sendTime) {
14074       if (onmove->useColors) {
14075         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14076       }
14077       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14078     }
14079     if (onmove->useColors) {
14080       SendToProgram(onmove->twoMachinesColor, onmove);
14081     }
14082     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14083 //    SendToProgram("go\n", onmove);
14084     onmove->maybeThinking = TRUE;
14085     SetMachineThinkingEnables();
14086
14087     StartClocks();
14088
14089     if(bookHit) { // [HGM] book: simulate book reply
14090         static char bookMove[MSG_SIZ]; // a bit generous?
14091
14092         programStats.nodes = programStats.depth = programStats.time =
14093         programStats.score = programStats.got_only_move = 0;
14094         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14095
14096         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14097         strcat(bookMove, bookHit);
14098         savedMessage = bookMove; // args for deferred call
14099         savedState = onmove;
14100         ScheduleDelayedEvent(DeferredBookMove, 1);
14101     }
14102 }
14103
14104 void
14105 TrainingEvent ()
14106 {
14107     if (gameMode == Training) {
14108       SetTrainingModeOff();
14109       gameMode = PlayFromGameFile;
14110       DisplayMessage("", _("Training mode off"));
14111     } else {
14112       gameMode = Training;
14113       animateTraining = appData.animate;
14114
14115       /* make sure we are not already at the end of the game */
14116       if (currentMove < forwardMostMove) {
14117         SetTrainingModeOn();
14118         DisplayMessage("", _("Training mode on"));
14119       } else {
14120         gameMode = PlayFromGameFile;
14121         DisplayError(_("Already at end of game"), 0);
14122       }
14123     }
14124     ModeHighlight();
14125 }
14126
14127 void
14128 IcsClientEvent ()
14129 {
14130     if (!appData.icsActive) return;
14131     switch (gameMode) {
14132       case IcsPlayingWhite:
14133       case IcsPlayingBlack:
14134       case IcsObserving:
14135       case IcsIdle:
14136       case BeginningOfGame:
14137       case IcsExamining:
14138         return;
14139
14140       case EditGame:
14141         break;
14142
14143       case EditPosition:
14144         EditPositionDone(TRUE);
14145         break;
14146
14147       case AnalyzeMode:
14148       case AnalyzeFile:
14149         ExitAnalyzeMode();
14150         break;
14151
14152       default:
14153         EditGameEvent();
14154         break;
14155     }
14156
14157     gameMode = IcsIdle;
14158     ModeHighlight();
14159     return;
14160 }
14161
14162 void
14163 EditGameEvent ()
14164 {
14165     int i;
14166
14167     switch (gameMode) {
14168       case Training:
14169         SetTrainingModeOff();
14170         break;
14171       case MachinePlaysWhite:
14172       case MachinePlaysBlack:
14173       case BeginningOfGame:
14174         SendToProgram("force\n", &first);
14175         SetUserThinkingEnables();
14176         break;
14177       case PlayFromGameFile:
14178         (void) StopLoadGameTimer();
14179         if (gameFileFP != NULL) {
14180             gameFileFP = NULL;
14181         }
14182         break;
14183       case EditPosition:
14184         EditPositionDone(TRUE);
14185         break;
14186       case AnalyzeMode:
14187       case AnalyzeFile:
14188         ExitAnalyzeMode();
14189         SendToProgram("force\n", &first);
14190         break;
14191       case TwoMachinesPlay:
14192         GameEnds(EndOfFile, NULL, GE_PLAYER);
14193         ResurrectChessProgram();
14194         SetUserThinkingEnables();
14195         break;
14196       case EndOfGame:
14197         ResurrectChessProgram();
14198         break;
14199       case IcsPlayingBlack:
14200       case IcsPlayingWhite:
14201         DisplayError(_("Warning: You are still playing a game"), 0);
14202         break;
14203       case IcsObserving:
14204         DisplayError(_("Warning: You are still observing a game"), 0);
14205         break;
14206       case IcsExamining:
14207         DisplayError(_("Warning: You are still examining a game"), 0);
14208         break;
14209       case IcsIdle:
14210         break;
14211       case EditGame:
14212       default:
14213         return;
14214     }
14215
14216     pausing = FALSE;
14217     StopClocks();
14218     first.offeredDraw = second.offeredDraw = 0;
14219
14220     if (gameMode == PlayFromGameFile) {
14221         whiteTimeRemaining = timeRemaining[0][currentMove];
14222         blackTimeRemaining = timeRemaining[1][currentMove];
14223         DisplayTitle("");
14224     }
14225
14226     if (gameMode == MachinePlaysWhite ||
14227         gameMode == MachinePlaysBlack ||
14228         gameMode == TwoMachinesPlay ||
14229         gameMode == EndOfGame) {
14230         i = forwardMostMove;
14231         while (i > currentMove) {
14232             SendToProgram("undo\n", &first);
14233             i--;
14234         }
14235         if(!adjustedClock) {
14236         whiteTimeRemaining = timeRemaining[0][currentMove];
14237         blackTimeRemaining = timeRemaining[1][currentMove];
14238         DisplayBothClocks();
14239         }
14240         if (whiteFlag || blackFlag) {
14241             whiteFlag = blackFlag = 0;
14242         }
14243         DisplayTitle("");
14244     }
14245
14246     gameMode = EditGame;
14247     ModeHighlight();
14248     SetGameInfo();
14249 }
14250
14251
14252 void
14253 EditPositionEvent ()
14254 {
14255     if (gameMode == EditPosition) {
14256         EditGameEvent();
14257         return;
14258     }
14259
14260     EditGameEvent();
14261     if (gameMode != EditGame) return;
14262
14263     gameMode = EditPosition;
14264     ModeHighlight();
14265     SetGameInfo();
14266     if (currentMove > 0)
14267       CopyBoard(boards[0], boards[currentMove]);
14268
14269     blackPlaysFirst = !WhiteOnMove(currentMove);
14270     ResetClocks();
14271     currentMove = forwardMostMove = backwardMostMove = 0;
14272     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14273     DisplayMove(-1);
14274     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14275 }
14276
14277 void
14278 ExitAnalyzeMode ()
14279 {
14280     /* [DM] icsEngineAnalyze - possible call from other functions */
14281     if (appData.icsEngineAnalyze) {
14282         appData.icsEngineAnalyze = FALSE;
14283
14284         DisplayMessage("",_("Close ICS engine analyze..."));
14285     }
14286     if (first.analysisSupport && first.analyzing) {
14287       SendToBoth("exit\n");
14288       first.analyzing = second.analyzing = FALSE;
14289     }
14290     thinkOutput[0] = NULLCHAR;
14291 }
14292
14293 void
14294 EditPositionDone (Boolean fakeRights)
14295 {
14296     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14297
14298     startedFromSetupPosition = TRUE;
14299     InitChessProgram(&first, FALSE);
14300     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14301       boards[0][EP_STATUS] = EP_NONE;
14302       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14303       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14304         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14305         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14306       } else boards[0][CASTLING][2] = NoRights;
14307       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14308         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14309         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14310       } else boards[0][CASTLING][5] = NoRights;
14311       if(gameInfo.variant == VariantSChess) {
14312         int i;
14313         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14314           boards[0][VIRGIN][i] = 0;
14315           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14316           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14317         }
14318       }
14319     }
14320     SendToProgram("force\n", &first);
14321     if (blackPlaysFirst) {
14322         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14323         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14324         currentMove = forwardMostMove = backwardMostMove = 1;
14325         CopyBoard(boards[1], boards[0]);
14326     } else {
14327         currentMove = forwardMostMove = backwardMostMove = 0;
14328     }
14329     SendBoard(&first, forwardMostMove);
14330     if (appData.debugMode) {
14331         fprintf(debugFP, "EditPosDone\n");
14332     }
14333     DisplayTitle("");
14334     DisplayMessage("", "");
14335     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14336     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14337     gameMode = EditGame;
14338     ModeHighlight();
14339     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14340     ClearHighlights(); /* [AS] */
14341 }
14342
14343 /* Pause for `ms' milliseconds */
14344 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14345 void
14346 TimeDelay (long ms)
14347 {
14348     TimeMark m1, m2;
14349
14350     GetTimeMark(&m1);
14351     do {
14352         GetTimeMark(&m2);
14353     } while (SubtractTimeMarks(&m2, &m1) < ms);
14354 }
14355
14356 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14357 void
14358 SendMultiLineToICS (char *buf)
14359 {
14360     char temp[MSG_SIZ+1], *p;
14361     int len;
14362
14363     len = strlen(buf);
14364     if (len > MSG_SIZ)
14365       len = MSG_SIZ;
14366
14367     strncpy(temp, buf, len);
14368     temp[len] = 0;
14369
14370     p = temp;
14371     while (*p) {
14372         if (*p == '\n' || *p == '\r')
14373           *p = ' ';
14374         ++p;
14375     }
14376
14377     strcat(temp, "\n");
14378     SendToICS(temp);
14379     SendToPlayer(temp, strlen(temp));
14380 }
14381
14382 void
14383 SetWhiteToPlayEvent ()
14384 {
14385     if (gameMode == EditPosition) {
14386         blackPlaysFirst = FALSE;
14387         DisplayBothClocks();    /* works because currentMove is 0 */
14388     } else if (gameMode == IcsExamining) {
14389         SendToICS(ics_prefix);
14390         SendToICS("tomove white\n");
14391     }
14392 }
14393
14394 void
14395 SetBlackToPlayEvent ()
14396 {
14397     if (gameMode == EditPosition) {
14398         blackPlaysFirst = TRUE;
14399         currentMove = 1;        /* kludge */
14400         DisplayBothClocks();
14401         currentMove = 0;
14402     } else if (gameMode == IcsExamining) {
14403         SendToICS(ics_prefix);
14404         SendToICS("tomove black\n");
14405     }
14406 }
14407
14408 void
14409 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14410 {
14411     char buf[MSG_SIZ];
14412     ChessSquare piece = boards[0][y][x];
14413
14414     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14415
14416     switch (selection) {
14417       case ClearBoard:
14418         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14419             SendToICS(ics_prefix);
14420             SendToICS("bsetup clear\n");
14421         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14422             SendToICS(ics_prefix);
14423             SendToICS("clearboard\n");
14424         } else {
14425             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14426                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14427                 for (y = 0; y < BOARD_HEIGHT; y++) {
14428                     if (gameMode == IcsExamining) {
14429                         if (boards[currentMove][y][x] != EmptySquare) {
14430                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14431                                     AAA + x, ONE + y);
14432                             SendToICS(buf);
14433                         }
14434                     } else {
14435                         boards[0][y][x] = p;
14436                     }
14437                 }
14438             }
14439         }
14440         if (gameMode == EditPosition) {
14441             DrawPosition(FALSE, boards[0]);
14442         }
14443         break;
14444
14445       case WhitePlay:
14446         SetWhiteToPlayEvent();
14447         break;
14448
14449       case BlackPlay:
14450         SetBlackToPlayEvent();
14451         break;
14452
14453       case EmptySquare:
14454         if (gameMode == IcsExamining) {
14455             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14456             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14457             SendToICS(buf);
14458         } else {
14459             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14460                 if(x == BOARD_LEFT-2) {
14461                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14462                     boards[0][y][1] = 0;
14463                 } else
14464                 if(x == BOARD_RGHT+1) {
14465                     if(y >= gameInfo.holdingsSize) break;
14466                     boards[0][y][BOARD_WIDTH-2] = 0;
14467                 } else break;
14468             }
14469             boards[0][y][x] = EmptySquare;
14470             DrawPosition(FALSE, boards[0]);
14471         }
14472         break;
14473
14474       case PromotePiece:
14475         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14476            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14477             selection = (ChessSquare) (PROMOTED piece);
14478         } else if(piece == EmptySquare) selection = WhiteSilver;
14479         else selection = (ChessSquare)((int)piece - 1);
14480         goto defaultlabel;
14481
14482       case DemotePiece:
14483         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14484            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14485             selection = (ChessSquare) (DEMOTED piece);
14486         } else if(piece == EmptySquare) selection = BlackSilver;
14487         else selection = (ChessSquare)((int)piece + 1);
14488         goto defaultlabel;
14489
14490       case WhiteQueen:
14491       case BlackQueen:
14492         if(gameInfo.variant == VariantShatranj ||
14493            gameInfo.variant == VariantXiangqi  ||
14494            gameInfo.variant == VariantCourier  ||
14495            gameInfo.variant == VariantMakruk     )
14496             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14497         goto defaultlabel;
14498
14499       case WhiteKing:
14500       case BlackKing:
14501         if(gameInfo.variant == VariantXiangqi)
14502             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14503         if(gameInfo.variant == VariantKnightmate)
14504             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14505       default:
14506         defaultlabel:
14507         if (gameMode == IcsExamining) {
14508             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14509             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14510                      PieceToChar(selection), AAA + x, ONE + y);
14511             SendToICS(buf);
14512         } else {
14513             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14514                 int n;
14515                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14516                     n = PieceToNumber(selection - BlackPawn);
14517                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14518                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14519                     boards[0][BOARD_HEIGHT-1-n][1]++;
14520                 } else
14521                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14522                     n = PieceToNumber(selection);
14523                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14524                     boards[0][n][BOARD_WIDTH-1] = selection;
14525                     boards[0][n][BOARD_WIDTH-2]++;
14526                 }
14527             } else
14528             boards[0][y][x] = selection;
14529             DrawPosition(TRUE, boards[0]);
14530             ClearHighlights();
14531             fromX = fromY = -1;
14532         }
14533         break;
14534     }
14535 }
14536
14537
14538 void
14539 DropMenuEvent (ChessSquare selection, int x, int y)
14540 {
14541     ChessMove moveType;
14542
14543     switch (gameMode) {
14544       case IcsPlayingWhite:
14545       case MachinePlaysBlack:
14546         if (!WhiteOnMove(currentMove)) {
14547             DisplayMoveError(_("It is Black's turn"));
14548             return;
14549         }
14550         moveType = WhiteDrop;
14551         break;
14552       case IcsPlayingBlack:
14553       case MachinePlaysWhite:
14554         if (WhiteOnMove(currentMove)) {
14555             DisplayMoveError(_("It is White's turn"));
14556             return;
14557         }
14558         moveType = BlackDrop;
14559         break;
14560       case EditGame:
14561         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14562         break;
14563       default:
14564         return;
14565     }
14566
14567     if (moveType == BlackDrop && selection < BlackPawn) {
14568       selection = (ChessSquare) ((int) selection
14569                                  + (int) BlackPawn - (int) WhitePawn);
14570     }
14571     if (boards[currentMove][y][x] != EmptySquare) {
14572         DisplayMoveError(_("That square is occupied"));
14573         return;
14574     }
14575
14576     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14577 }
14578
14579 void
14580 AcceptEvent ()
14581 {
14582     /* Accept a pending offer of any kind from opponent */
14583
14584     if (appData.icsActive) {
14585         SendToICS(ics_prefix);
14586         SendToICS("accept\n");
14587     } else if (cmailMsgLoaded) {
14588         if (currentMove == cmailOldMove &&
14589             commentList[cmailOldMove] != NULL &&
14590             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14591                    "Black offers a draw" : "White offers a draw")) {
14592             TruncateGame();
14593             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14594             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14595         } else {
14596             DisplayError(_("There is no pending offer on this move"), 0);
14597             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14598         }
14599     } else {
14600         /* Not used for offers from chess program */
14601     }
14602 }
14603
14604 void
14605 DeclineEvent ()
14606 {
14607     /* Decline a pending offer of any kind from opponent */
14608
14609     if (appData.icsActive) {
14610         SendToICS(ics_prefix);
14611         SendToICS("decline\n");
14612     } else if (cmailMsgLoaded) {
14613         if (currentMove == cmailOldMove &&
14614             commentList[cmailOldMove] != NULL &&
14615             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14616                    "Black offers a draw" : "White offers a draw")) {
14617 #ifdef NOTDEF
14618             AppendComment(cmailOldMove, "Draw declined", TRUE);
14619             DisplayComment(cmailOldMove - 1, "Draw declined");
14620 #endif /*NOTDEF*/
14621         } else {
14622             DisplayError(_("There is no pending offer on this move"), 0);
14623         }
14624     } else {
14625         /* Not used for offers from chess program */
14626     }
14627 }
14628
14629 void
14630 RematchEvent ()
14631 {
14632     /* Issue ICS rematch command */
14633     if (appData.icsActive) {
14634         SendToICS(ics_prefix);
14635         SendToICS("rematch\n");
14636     }
14637 }
14638
14639 void
14640 CallFlagEvent ()
14641 {
14642     /* Call your opponent's flag (claim a win on time) */
14643     if (appData.icsActive) {
14644         SendToICS(ics_prefix);
14645         SendToICS("flag\n");
14646     } else {
14647         switch (gameMode) {
14648           default:
14649             return;
14650           case MachinePlaysWhite:
14651             if (whiteFlag) {
14652                 if (blackFlag)
14653                   GameEnds(GameIsDrawn, "Both players ran out of time",
14654                            GE_PLAYER);
14655                 else
14656                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14657             } else {
14658                 DisplayError(_("Your opponent is not out of time"), 0);
14659             }
14660             break;
14661           case MachinePlaysBlack:
14662             if (blackFlag) {
14663                 if (whiteFlag)
14664                   GameEnds(GameIsDrawn, "Both players ran out of time",
14665                            GE_PLAYER);
14666                 else
14667                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14668             } else {
14669                 DisplayError(_("Your opponent is not out of time"), 0);
14670             }
14671             break;
14672         }
14673     }
14674 }
14675
14676 void
14677 ClockClick (int which)
14678 {       // [HGM] code moved to back-end from winboard.c
14679         if(which) { // black clock
14680           if (gameMode == EditPosition || gameMode == IcsExamining) {
14681             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14682             SetBlackToPlayEvent();
14683           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14684           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14685           } else if (shiftKey) {
14686             AdjustClock(which, -1);
14687           } else if (gameMode == IcsPlayingWhite ||
14688                      gameMode == MachinePlaysBlack) {
14689             CallFlagEvent();
14690           }
14691         } else { // white clock
14692           if (gameMode == EditPosition || gameMode == IcsExamining) {
14693             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14694             SetWhiteToPlayEvent();
14695           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14696           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14697           } else if (shiftKey) {
14698             AdjustClock(which, -1);
14699           } else if (gameMode == IcsPlayingBlack ||
14700                    gameMode == MachinePlaysWhite) {
14701             CallFlagEvent();
14702           }
14703         }
14704 }
14705
14706 void
14707 DrawEvent ()
14708 {
14709     /* Offer draw or accept pending draw offer from opponent */
14710
14711     if (appData.icsActive) {
14712         /* Note: tournament rules require draw offers to be
14713            made after you make your move but before you punch
14714            your clock.  Currently ICS doesn't let you do that;
14715            instead, you immediately punch your clock after making
14716            a move, but you can offer a draw at any time. */
14717
14718         SendToICS(ics_prefix);
14719         SendToICS("draw\n");
14720         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14721     } else if (cmailMsgLoaded) {
14722         if (currentMove == cmailOldMove &&
14723             commentList[cmailOldMove] != NULL &&
14724             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14725                    "Black offers a draw" : "White offers a draw")) {
14726             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14727             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14728         } else if (currentMove == cmailOldMove + 1) {
14729             char *offer = WhiteOnMove(cmailOldMove) ?
14730               "White offers a draw" : "Black offers a draw";
14731             AppendComment(currentMove, offer, TRUE);
14732             DisplayComment(currentMove - 1, offer);
14733             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14734         } else {
14735             DisplayError(_("You must make your move before offering a draw"), 0);
14736             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14737         }
14738     } else if (first.offeredDraw) {
14739         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14740     } else {
14741         if (first.sendDrawOffers) {
14742             SendToProgram("draw\n", &first);
14743             userOfferedDraw = TRUE;
14744         }
14745     }
14746 }
14747
14748 void
14749 AdjournEvent ()
14750 {
14751     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14752
14753     if (appData.icsActive) {
14754         SendToICS(ics_prefix);
14755         SendToICS("adjourn\n");
14756     } else {
14757         /* Currently GNU Chess doesn't offer or accept Adjourns */
14758     }
14759 }
14760
14761
14762 void
14763 AbortEvent ()
14764 {
14765     /* Offer Abort or accept pending Abort offer from opponent */
14766
14767     if (appData.icsActive) {
14768         SendToICS(ics_prefix);
14769         SendToICS("abort\n");
14770     } else {
14771         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14772     }
14773 }
14774
14775 void
14776 ResignEvent ()
14777 {
14778     /* Resign.  You can do this even if it's not your turn. */
14779
14780     if (appData.icsActive) {
14781         SendToICS(ics_prefix);
14782         SendToICS("resign\n");
14783     } else {
14784         switch (gameMode) {
14785           case MachinePlaysWhite:
14786             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14787             break;
14788           case MachinePlaysBlack:
14789             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14790             break;
14791           case EditGame:
14792             if (cmailMsgLoaded) {
14793                 TruncateGame();
14794                 if (WhiteOnMove(cmailOldMove)) {
14795                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14796                 } else {
14797                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14798                 }
14799                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14800             }
14801             break;
14802           default:
14803             break;
14804         }
14805     }
14806 }
14807
14808
14809 void
14810 StopObservingEvent ()
14811 {
14812     /* Stop observing current games */
14813     SendToICS(ics_prefix);
14814     SendToICS("unobserve\n");
14815 }
14816
14817 void
14818 StopExaminingEvent ()
14819 {
14820     /* Stop observing current game */
14821     SendToICS(ics_prefix);
14822     SendToICS("unexamine\n");
14823 }
14824
14825 void
14826 ForwardInner (int target)
14827 {
14828     int limit; int oldSeekGraphUp = seekGraphUp;
14829
14830     if (appData.debugMode)
14831         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14832                 target, currentMove, forwardMostMove);
14833
14834     if (gameMode == EditPosition)
14835       return;
14836
14837     seekGraphUp = FALSE;
14838     MarkTargetSquares(1);
14839
14840     if (gameMode == PlayFromGameFile && !pausing)
14841       PauseEvent();
14842
14843     if (gameMode == IcsExamining && pausing)
14844       limit = pauseExamForwardMostMove;
14845     else
14846       limit = forwardMostMove;
14847
14848     if (target > limit) target = limit;
14849
14850     if (target > 0 && moveList[target - 1][0]) {
14851         int fromX, fromY, toX, toY;
14852         toX = moveList[target - 1][2] - AAA;
14853         toY = moveList[target - 1][3] - ONE;
14854         if (moveList[target - 1][1] == '@') {
14855             if (appData.highlightLastMove) {
14856                 SetHighlights(-1, -1, toX, toY);
14857             }
14858         } else {
14859             fromX = moveList[target - 1][0] - AAA;
14860             fromY = moveList[target - 1][1] - ONE;
14861             if (target == currentMove + 1) {
14862                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14863             }
14864             if (appData.highlightLastMove) {
14865                 SetHighlights(fromX, fromY, toX, toY);
14866             }
14867         }
14868     }
14869     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14870         gameMode == Training || gameMode == PlayFromGameFile ||
14871         gameMode == AnalyzeFile) {
14872         while (currentMove < target) {
14873             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14874             SendMoveToProgram(currentMove++, &first);
14875         }
14876     } else {
14877         currentMove = target;
14878     }
14879
14880     if (gameMode == EditGame || gameMode == EndOfGame) {
14881         whiteTimeRemaining = timeRemaining[0][currentMove];
14882         blackTimeRemaining = timeRemaining[1][currentMove];
14883     }
14884     DisplayBothClocks();
14885     DisplayMove(currentMove - 1);
14886     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14887     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14888     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14889         DisplayComment(currentMove - 1, commentList[currentMove]);
14890     }
14891     ClearMap(); // [HGM] exclude: invalidate map
14892 }
14893
14894
14895 void
14896 ForwardEvent ()
14897 {
14898     if (gameMode == IcsExamining && !pausing) {
14899         SendToICS(ics_prefix);
14900         SendToICS("forward\n");
14901     } else {
14902         ForwardInner(currentMove + 1);
14903     }
14904 }
14905
14906 void
14907 ToEndEvent ()
14908 {
14909     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14910         /* to optimze, we temporarily turn off analysis mode while we feed
14911          * the remaining moves to the engine. Otherwise we get analysis output
14912          * after each move.
14913          */
14914         if (first.analysisSupport) {
14915           SendToProgram("exit\nforce\n", &first);
14916           first.analyzing = FALSE;
14917         }
14918     }
14919
14920     if (gameMode == IcsExamining && !pausing) {
14921         SendToICS(ics_prefix);
14922         SendToICS("forward 999999\n");
14923     } else {
14924         ForwardInner(forwardMostMove);
14925     }
14926
14927     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14928         /* we have fed all the moves, so reactivate analysis mode */
14929         SendToProgram("analyze\n", &first);
14930         first.analyzing = TRUE;
14931         /*first.maybeThinking = TRUE;*/
14932         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14933     }
14934 }
14935
14936 void
14937 BackwardInner (int target)
14938 {
14939     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14940
14941     if (appData.debugMode)
14942         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14943                 target, currentMove, forwardMostMove);
14944
14945     if (gameMode == EditPosition) return;
14946     seekGraphUp = FALSE;
14947     MarkTargetSquares(1);
14948     if (currentMove <= backwardMostMove) {
14949         ClearHighlights();
14950         DrawPosition(full_redraw, boards[currentMove]);
14951         return;
14952     }
14953     if (gameMode == PlayFromGameFile && !pausing)
14954       PauseEvent();
14955
14956     if (moveList[target][0]) {
14957         int fromX, fromY, toX, toY;
14958         toX = moveList[target][2] - AAA;
14959         toY = moveList[target][3] - ONE;
14960         if (moveList[target][1] == '@') {
14961             if (appData.highlightLastMove) {
14962                 SetHighlights(-1, -1, toX, toY);
14963             }
14964         } else {
14965             fromX = moveList[target][0] - AAA;
14966             fromY = moveList[target][1] - ONE;
14967             if (target == currentMove - 1) {
14968                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14969             }
14970             if (appData.highlightLastMove) {
14971                 SetHighlights(fromX, fromY, toX, toY);
14972             }
14973         }
14974     }
14975     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14976         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14977         while (currentMove > target) {
14978             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14979                 // null move cannot be undone. Reload program with move history before it.
14980                 int i;
14981                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14982                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14983                 }
14984                 SendBoard(&first, i);
14985               if(second.analyzing) SendBoard(&second, i);
14986                 for(currentMove=i; currentMove<target; currentMove++) {
14987                     SendMoveToProgram(currentMove, &first);
14988                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14989                 }
14990                 break;
14991             }
14992             SendToBoth("undo\n");
14993             currentMove--;
14994         }
14995     } else {
14996         currentMove = target;
14997     }
14998
14999     if (gameMode == EditGame || gameMode == EndOfGame) {
15000         whiteTimeRemaining = timeRemaining[0][currentMove];
15001         blackTimeRemaining = timeRemaining[1][currentMove];
15002     }
15003     DisplayBothClocks();
15004     DisplayMove(currentMove - 1);
15005     DrawPosition(full_redraw, boards[currentMove]);
15006     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15007     // [HGM] PV info: routine tests if comment empty
15008     DisplayComment(currentMove - 1, commentList[currentMove]);
15009     ClearMap(); // [HGM] exclude: invalidate map
15010 }
15011
15012 void
15013 BackwardEvent ()
15014 {
15015     if (gameMode == IcsExamining && !pausing) {
15016         SendToICS(ics_prefix);
15017         SendToICS("backward\n");
15018     } else {
15019         BackwardInner(currentMove - 1);
15020     }
15021 }
15022
15023 void
15024 ToStartEvent ()
15025 {
15026     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15027         /* to optimize, we temporarily turn off analysis mode while we undo
15028          * all the moves. Otherwise we get analysis output after each undo.
15029          */
15030         if (first.analysisSupport) {
15031           SendToProgram("exit\nforce\n", &first);
15032           first.analyzing = FALSE;
15033         }
15034     }
15035
15036     if (gameMode == IcsExamining && !pausing) {
15037         SendToICS(ics_prefix);
15038         SendToICS("backward 999999\n");
15039     } else {
15040         BackwardInner(backwardMostMove);
15041     }
15042
15043     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15044         /* we have fed all the moves, so reactivate analysis mode */
15045         SendToProgram("analyze\n", &first);
15046         first.analyzing = TRUE;
15047         /*first.maybeThinking = TRUE;*/
15048         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15049     }
15050 }
15051
15052 void
15053 ToNrEvent (int to)
15054 {
15055   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15056   if (to >= forwardMostMove) to = forwardMostMove;
15057   if (to <= backwardMostMove) to = backwardMostMove;
15058   if (to < currentMove) {
15059     BackwardInner(to);
15060   } else {
15061     ForwardInner(to);
15062   }
15063 }
15064
15065 void
15066 RevertEvent (Boolean annotate)
15067 {
15068     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15069         return;
15070     }
15071     if (gameMode != IcsExamining) {
15072         DisplayError(_("You are not examining a game"), 0);
15073         return;
15074     }
15075     if (pausing) {
15076         DisplayError(_("You can't revert while pausing"), 0);
15077         return;
15078     }
15079     SendToICS(ics_prefix);
15080     SendToICS("revert\n");
15081 }
15082
15083 void
15084 RetractMoveEvent ()
15085 {
15086     switch (gameMode) {
15087       case MachinePlaysWhite:
15088       case MachinePlaysBlack:
15089         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15090             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15091             return;
15092         }
15093         if (forwardMostMove < 2) return;
15094         currentMove = forwardMostMove = forwardMostMove - 2;
15095         whiteTimeRemaining = timeRemaining[0][currentMove];
15096         blackTimeRemaining = timeRemaining[1][currentMove];
15097         DisplayBothClocks();
15098         DisplayMove(currentMove - 1);
15099         ClearHighlights();/*!! could figure this out*/
15100         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15101         SendToProgram("remove\n", &first);
15102         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15103         break;
15104
15105       case BeginningOfGame:
15106       default:
15107         break;
15108
15109       case IcsPlayingWhite:
15110       case IcsPlayingBlack:
15111         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15112             SendToICS(ics_prefix);
15113             SendToICS("takeback 2\n");
15114         } else {
15115             SendToICS(ics_prefix);
15116             SendToICS("takeback 1\n");
15117         }
15118         break;
15119     }
15120 }
15121
15122 void
15123 MoveNowEvent ()
15124 {
15125     ChessProgramState *cps;
15126
15127     switch (gameMode) {
15128       case MachinePlaysWhite:
15129         if (!WhiteOnMove(forwardMostMove)) {
15130             DisplayError(_("It is your turn"), 0);
15131             return;
15132         }
15133         cps = &first;
15134         break;
15135       case MachinePlaysBlack:
15136         if (WhiteOnMove(forwardMostMove)) {
15137             DisplayError(_("It is your turn"), 0);
15138             return;
15139         }
15140         cps = &first;
15141         break;
15142       case TwoMachinesPlay:
15143         if (WhiteOnMove(forwardMostMove) ==
15144             (first.twoMachinesColor[0] == 'w')) {
15145             cps = &first;
15146         } else {
15147             cps = &second;
15148         }
15149         break;
15150       case BeginningOfGame:
15151       default:
15152         return;
15153     }
15154     SendToProgram("?\n", cps);
15155 }
15156
15157 void
15158 TruncateGameEvent ()
15159 {
15160     EditGameEvent();
15161     if (gameMode != EditGame) return;
15162     TruncateGame();
15163 }
15164
15165 void
15166 TruncateGame ()
15167 {
15168     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15169     if (forwardMostMove > currentMove) {
15170         if (gameInfo.resultDetails != NULL) {
15171             free(gameInfo.resultDetails);
15172             gameInfo.resultDetails = NULL;
15173             gameInfo.result = GameUnfinished;
15174         }
15175         forwardMostMove = currentMove;
15176         HistorySet(parseList, backwardMostMove, forwardMostMove,
15177                    currentMove-1);
15178     }
15179 }
15180
15181 void
15182 HintEvent ()
15183 {
15184     if (appData.noChessProgram) return;
15185     switch (gameMode) {
15186       case MachinePlaysWhite:
15187         if (WhiteOnMove(forwardMostMove)) {
15188             DisplayError(_("Wait until your turn"), 0);
15189             return;
15190         }
15191         break;
15192       case BeginningOfGame:
15193       case MachinePlaysBlack:
15194         if (!WhiteOnMove(forwardMostMove)) {
15195             DisplayError(_("Wait until your turn"), 0);
15196             return;
15197         }
15198         break;
15199       default:
15200         DisplayError(_("No hint available"), 0);
15201         return;
15202     }
15203     SendToProgram("hint\n", &first);
15204     hintRequested = TRUE;
15205 }
15206
15207 void
15208 CreateBookEvent ()
15209 {
15210     ListGame * lg = (ListGame *) gameList.head;
15211     FILE *f;
15212     int nItem;
15213     static int secondTime = FALSE;
15214
15215     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15216         DisplayError(_("Game list not loaded or empty"), 0);
15217         return;
15218     }
15219
15220     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15221         fclose(f);
15222         secondTime++;
15223         DisplayNote(_("Book file exists! Try again for overwrite."));
15224         return;
15225     }
15226
15227     creatingBook = TRUE;
15228     secondTime = FALSE;
15229
15230     /* Get list size */
15231     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15232         LoadGame(f, nItem, "", TRUE);
15233         AddGameToBook(TRUE);
15234         lg = (ListGame *) lg->node.succ;
15235     }
15236
15237     creatingBook = FALSE;
15238     FlushBook();
15239 }
15240
15241 void
15242 BookEvent ()
15243 {
15244     if (appData.noChessProgram) return;
15245     switch (gameMode) {
15246       case MachinePlaysWhite:
15247         if (WhiteOnMove(forwardMostMove)) {
15248             DisplayError(_("Wait until your turn"), 0);
15249             return;
15250         }
15251         break;
15252       case BeginningOfGame:
15253       case MachinePlaysBlack:
15254         if (!WhiteOnMove(forwardMostMove)) {
15255             DisplayError(_("Wait until your turn"), 0);
15256             return;
15257         }
15258         break;
15259       case EditPosition:
15260         EditPositionDone(TRUE);
15261         break;
15262       case TwoMachinesPlay:
15263         return;
15264       default:
15265         break;
15266     }
15267     SendToProgram("bk\n", &first);
15268     bookOutput[0] = NULLCHAR;
15269     bookRequested = TRUE;
15270 }
15271
15272 void
15273 AboutGameEvent ()
15274 {
15275     char *tags = PGNTags(&gameInfo);
15276     TagsPopUp(tags, CmailMsg());
15277     free(tags);
15278 }
15279
15280 /* end button procedures */
15281
15282 void
15283 PrintPosition (FILE *fp, int move)
15284 {
15285     int i, j;
15286
15287     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15288         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15289             char c = PieceToChar(boards[move][i][j]);
15290             fputc(c == 'x' ? '.' : c, fp);
15291             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15292         }
15293     }
15294     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15295       fprintf(fp, "white to play\n");
15296     else
15297       fprintf(fp, "black to play\n");
15298 }
15299
15300 void
15301 PrintOpponents (FILE *fp)
15302 {
15303     if (gameInfo.white != NULL) {
15304         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15305     } else {
15306         fprintf(fp, "\n");
15307     }
15308 }
15309
15310 /* Find last component of program's own name, using some heuristics */
15311 void
15312 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15313 {
15314     char *p, *q, c;
15315     int local = (strcmp(host, "localhost") == 0);
15316     while (!local && (p = strchr(prog, ';')) != NULL) {
15317         p++;
15318         while (*p == ' ') p++;
15319         prog = p;
15320     }
15321     if (*prog == '"' || *prog == '\'') {
15322         q = strchr(prog + 1, *prog);
15323     } else {
15324         q = strchr(prog, ' ');
15325     }
15326     if (q == NULL) q = prog + strlen(prog);
15327     p = q;
15328     while (p >= prog && *p != '/' && *p != '\\') p--;
15329     p++;
15330     if(p == prog && *p == '"') p++;
15331     c = *q; *q = 0;
15332     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15333     memcpy(buf, p, q - p);
15334     buf[q - p] = NULLCHAR;
15335     if (!local) {
15336         strcat(buf, "@");
15337         strcat(buf, host);
15338     }
15339 }
15340
15341 char *
15342 TimeControlTagValue ()
15343 {
15344     char buf[MSG_SIZ];
15345     if (!appData.clockMode) {
15346       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15347     } else if (movesPerSession > 0) {
15348       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15349     } else if (timeIncrement == 0) {
15350       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15351     } else {
15352       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15353     }
15354     return StrSave(buf);
15355 }
15356
15357 void
15358 SetGameInfo ()
15359 {
15360     /* This routine is used only for certain modes */
15361     VariantClass v = gameInfo.variant;
15362     ChessMove r = GameUnfinished;
15363     char *p = NULL;
15364
15365     if(keepInfo) return;
15366
15367     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15368         r = gameInfo.result;
15369         p = gameInfo.resultDetails;
15370         gameInfo.resultDetails = NULL;
15371     }
15372     ClearGameInfo(&gameInfo);
15373     gameInfo.variant = v;
15374
15375     switch (gameMode) {
15376       case MachinePlaysWhite:
15377         gameInfo.event = StrSave( appData.pgnEventHeader );
15378         gameInfo.site = StrSave(HostName());
15379         gameInfo.date = PGNDate();
15380         gameInfo.round = StrSave("-");
15381         gameInfo.white = StrSave(first.tidy);
15382         gameInfo.black = StrSave(UserName());
15383         gameInfo.timeControl = TimeControlTagValue();
15384         break;
15385
15386       case MachinePlaysBlack:
15387         gameInfo.event = StrSave( appData.pgnEventHeader );
15388         gameInfo.site = StrSave(HostName());
15389         gameInfo.date = PGNDate();
15390         gameInfo.round = StrSave("-");
15391         gameInfo.white = StrSave(UserName());
15392         gameInfo.black = StrSave(first.tidy);
15393         gameInfo.timeControl = TimeControlTagValue();
15394         break;
15395
15396       case TwoMachinesPlay:
15397         gameInfo.event = StrSave( appData.pgnEventHeader );
15398         gameInfo.site = StrSave(HostName());
15399         gameInfo.date = PGNDate();
15400         if (roundNr > 0) {
15401             char buf[MSG_SIZ];
15402             snprintf(buf, MSG_SIZ, "%d", roundNr);
15403             gameInfo.round = StrSave(buf);
15404         } else {
15405             gameInfo.round = StrSave("-");
15406         }
15407         if (first.twoMachinesColor[0] == 'w') {
15408             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15409             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15410         } else {
15411             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15412             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15413         }
15414         gameInfo.timeControl = TimeControlTagValue();
15415         break;
15416
15417       case EditGame:
15418         gameInfo.event = StrSave("Edited game");
15419         gameInfo.site = StrSave(HostName());
15420         gameInfo.date = PGNDate();
15421         gameInfo.round = StrSave("-");
15422         gameInfo.white = StrSave("-");
15423         gameInfo.black = StrSave("-");
15424         gameInfo.result = r;
15425         gameInfo.resultDetails = p;
15426         break;
15427
15428       case EditPosition:
15429         gameInfo.event = StrSave("Edited position");
15430         gameInfo.site = StrSave(HostName());
15431         gameInfo.date = PGNDate();
15432         gameInfo.round = StrSave("-");
15433         gameInfo.white = StrSave("-");
15434         gameInfo.black = StrSave("-");
15435         break;
15436
15437       case IcsPlayingWhite:
15438       case IcsPlayingBlack:
15439       case IcsObserving:
15440       case IcsExamining:
15441         break;
15442
15443       case PlayFromGameFile:
15444         gameInfo.event = StrSave("Game from non-PGN file");
15445         gameInfo.site = StrSave(HostName());
15446         gameInfo.date = PGNDate();
15447         gameInfo.round = StrSave("-");
15448         gameInfo.white = StrSave("?");
15449         gameInfo.black = StrSave("?");
15450         break;
15451
15452       default:
15453         break;
15454     }
15455 }
15456
15457 void
15458 ReplaceComment (int index, char *text)
15459 {
15460     int len;
15461     char *p;
15462     float score;
15463
15464     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15465        pvInfoList[index-1].depth == len &&
15466        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15467        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15468     while (*text == '\n') text++;
15469     len = strlen(text);
15470     while (len > 0 && text[len - 1] == '\n') len--;
15471
15472     if (commentList[index] != NULL)
15473       free(commentList[index]);
15474
15475     if (len == 0) {
15476         commentList[index] = NULL;
15477         return;
15478     }
15479   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15480       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15481       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15482     commentList[index] = (char *) malloc(len + 2);
15483     strncpy(commentList[index], text, len);
15484     commentList[index][len] = '\n';
15485     commentList[index][len + 1] = NULLCHAR;
15486   } else {
15487     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15488     char *p;
15489     commentList[index] = (char *) malloc(len + 7);
15490     safeStrCpy(commentList[index], "{\n", 3);
15491     safeStrCpy(commentList[index]+2, text, len+1);
15492     commentList[index][len+2] = NULLCHAR;
15493     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15494     strcat(commentList[index], "\n}\n");
15495   }
15496 }
15497
15498 void
15499 CrushCRs (char *text)
15500 {
15501   char *p = text;
15502   char *q = text;
15503   char ch;
15504
15505   do {
15506     ch = *p++;
15507     if (ch == '\r') continue;
15508     *q++ = ch;
15509   } while (ch != '\0');
15510 }
15511
15512 void
15513 AppendComment (int index, char *text, Boolean addBraces)
15514 /* addBraces  tells if we should add {} */
15515 {
15516     int oldlen, len;
15517     char *old;
15518
15519 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15520     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15521
15522     CrushCRs(text);
15523     while (*text == '\n') text++;
15524     len = strlen(text);
15525     while (len > 0 && text[len - 1] == '\n') len--;
15526     text[len] = NULLCHAR;
15527
15528     if (len == 0) return;
15529
15530     if (commentList[index] != NULL) {
15531       Boolean addClosingBrace = addBraces;
15532         old = commentList[index];
15533         oldlen = strlen(old);
15534         while(commentList[index][oldlen-1] ==  '\n')
15535           commentList[index][--oldlen] = NULLCHAR;
15536         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15537         safeStrCpy(commentList[index], old, oldlen + len + 6);
15538         free(old);
15539         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15540         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15541           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15542           while (*text == '\n') { text++; len--; }
15543           commentList[index][--oldlen] = NULLCHAR;
15544       }
15545         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15546         else          strcat(commentList[index], "\n");
15547         strcat(commentList[index], text);
15548         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15549         else          strcat(commentList[index], "\n");
15550     } else {
15551         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15552         if(addBraces)
15553           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15554         else commentList[index][0] = NULLCHAR;
15555         strcat(commentList[index], text);
15556         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15557         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15558     }
15559 }
15560
15561 static char *
15562 FindStr (char * text, char * sub_text)
15563 {
15564     char * result = strstr( text, sub_text );
15565
15566     if( result != NULL ) {
15567         result += strlen( sub_text );
15568     }
15569
15570     return result;
15571 }
15572
15573 /* [AS] Try to extract PV info from PGN comment */
15574 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15575 char *
15576 GetInfoFromComment (int index, char * text)
15577 {
15578     char * sep = text, *p;
15579
15580     if( text != NULL && index > 0 ) {
15581         int score = 0;
15582         int depth = 0;
15583         int time = -1, sec = 0, deci;
15584         char * s_eval = FindStr( text, "[%eval " );
15585         char * s_emt = FindStr( text, "[%emt " );
15586
15587         if( s_eval != NULL || s_emt != NULL ) {
15588             /* New style */
15589             char delim;
15590
15591             if( s_eval != NULL ) {
15592                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15593                     return text;
15594                 }
15595
15596                 if( delim != ']' ) {
15597                     return text;
15598                 }
15599             }
15600
15601             if( s_emt != NULL ) {
15602             }
15603                 return text;
15604         }
15605         else {
15606             /* We expect something like: [+|-]nnn.nn/dd */
15607             int score_lo = 0;
15608
15609             if(*text != '{') return text; // [HGM] braces: must be normal comment
15610
15611             sep = strchr( text, '/' );
15612             if( sep == NULL || sep < (text+4) ) {
15613                 return text;
15614             }
15615
15616             p = text;
15617             if(p[1] == '(') { // comment starts with PV
15618                p = strchr(p, ')'); // locate end of PV
15619                if(p == NULL || sep < p+5) return text;
15620                // at this point we have something like "{(.*) +0.23/6 ..."
15621                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15622                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15623                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15624             }
15625             time = -1; sec = -1; deci = -1;
15626             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15627                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15628                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15629                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15630                 return text;
15631             }
15632
15633             if( score_lo < 0 || score_lo >= 100 ) {
15634                 return text;
15635             }
15636
15637             if(sec >= 0) time = 600*time + 10*sec; else
15638             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15639
15640             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15641
15642             /* [HGM] PV time: now locate end of PV info */
15643             while( *++sep >= '0' && *sep <= '9'); // strip depth
15644             if(time >= 0)
15645             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15646             if(sec >= 0)
15647             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15648             if(deci >= 0)
15649             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15650             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15651         }
15652
15653         if( depth <= 0 ) {
15654             return text;
15655         }
15656
15657         if( time < 0 ) {
15658             time = -1;
15659         }
15660
15661         pvInfoList[index-1].depth = depth;
15662         pvInfoList[index-1].score = score;
15663         pvInfoList[index-1].time  = 10*time; // centi-sec
15664         if(*sep == '}') *sep = 0; else *--sep = '{';
15665         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15666     }
15667     return sep;
15668 }
15669
15670 void
15671 SendToProgram (char *message, ChessProgramState *cps)
15672 {
15673     int count, outCount, error;
15674     char buf[MSG_SIZ];
15675
15676     if (cps->pr == NoProc) return;
15677     Attention(cps);
15678
15679     if (appData.debugMode) {
15680         TimeMark now;
15681         GetTimeMark(&now);
15682         fprintf(debugFP, "%ld >%-6s: %s",
15683                 SubtractTimeMarks(&now, &programStartTime),
15684                 cps->which, message);
15685         if(serverFP)
15686             fprintf(serverFP, "%ld >%-6s: %s",
15687                 SubtractTimeMarks(&now, &programStartTime),
15688                 cps->which, message), fflush(serverFP);
15689     }
15690
15691     count = strlen(message);
15692     outCount = OutputToProcess(cps->pr, message, count, &error);
15693     if (outCount < count && !exiting
15694                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15695       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15696       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15697         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15698             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15699                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15700                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15701                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15702             } else {
15703                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15704                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15705                 gameInfo.result = res;
15706             }
15707             gameInfo.resultDetails = StrSave(buf);
15708         }
15709         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15710         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15711     }
15712 }
15713
15714 void
15715 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15716 {
15717     char *end_str;
15718     char buf[MSG_SIZ];
15719     ChessProgramState *cps = (ChessProgramState *)closure;
15720
15721     if (isr != cps->isr) return; /* Killed intentionally */
15722     if (count <= 0) {
15723         if (count == 0) {
15724             RemoveInputSource(cps->isr);
15725             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15726                     _(cps->which), cps->program);
15727             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15728             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15729                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15730                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15731                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15732                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15733                 } else {
15734                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15735                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15736                     gameInfo.result = res;
15737                 }
15738                 gameInfo.resultDetails = StrSave(buf);
15739             }
15740             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15741             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15742         } else {
15743             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15744                     _(cps->which), cps->program);
15745             RemoveInputSource(cps->isr);
15746
15747             /* [AS] Program is misbehaving badly... kill it */
15748             if( count == -2 ) {
15749                 DestroyChildProcess( cps->pr, 9 );
15750                 cps->pr = NoProc;
15751             }
15752
15753             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15754         }
15755         return;
15756     }
15757
15758     if ((end_str = strchr(message, '\r')) != NULL)
15759       *end_str = NULLCHAR;
15760     if ((end_str = strchr(message, '\n')) != NULL)
15761       *end_str = NULLCHAR;
15762
15763     if (appData.debugMode) {
15764         TimeMark now; int print = 1;
15765         char *quote = ""; char c; int i;
15766
15767         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15768                 char start = message[0];
15769                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15770                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15771                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15772                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15773                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15774                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15775                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15776                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15777                    sscanf(message, "hint: %c", &c)!=1 &&
15778                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15779                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15780                     print = (appData.engineComments >= 2);
15781                 }
15782                 message[0] = start; // restore original message
15783         }
15784         if(print) {
15785                 GetTimeMark(&now);
15786                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15787                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15788                         quote,
15789                         message);
15790                 if(serverFP)
15791                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15792                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15793                         quote,
15794                         message), fflush(serverFP);
15795         }
15796     }
15797
15798     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15799     if (appData.icsEngineAnalyze) {
15800         if (strstr(message, "whisper") != NULL ||
15801              strstr(message, "kibitz") != NULL ||
15802             strstr(message, "tellics") != NULL) return;
15803     }
15804
15805     HandleMachineMove(message, cps);
15806 }
15807
15808
15809 void
15810 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15811 {
15812     char buf[MSG_SIZ];
15813     int seconds;
15814
15815     if( timeControl_2 > 0 ) {
15816         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15817             tc = timeControl_2;
15818         }
15819     }
15820     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15821     inc /= cps->timeOdds;
15822     st  /= cps->timeOdds;
15823
15824     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15825
15826     if (st > 0) {
15827       /* Set exact time per move, normally using st command */
15828       if (cps->stKludge) {
15829         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15830         seconds = st % 60;
15831         if (seconds == 0) {
15832           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15833         } else {
15834           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15835         }
15836       } else {
15837         snprintf(buf, MSG_SIZ, "st %d\n", st);
15838       }
15839     } else {
15840       /* Set conventional or incremental time control, using level command */
15841       if (seconds == 0) {
15842         /* Note old gnuchess bug -- minutes:seconds used to not work.
15843            Fixed in later versions, but still avoid :seconds
15844            when seconds is 0. */
15845         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15846       } else {
15847         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15848                  seconds, inc/1000.);
15849       }
15850     }
15851     SendToProgram(buf, cps);
15852
15853     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15854     /* Orthogonally, limit search to given depth */
15855     if (sd > 0) {
15856       if (cps->sdKludge) {
15857         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15858       } else {
15859         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15860       }
15861       SendToProgram(buf, cps);
15862     }
15863
15864     if(cps->nps >= 0) { /* [HGM] nps */
15865         if(cps->supportsNPS == FALSE)
15866           cps->nps = -1; // don't use if engine explicitly says not supported!
15867         else {
15868           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15869           SendToProgram(buf, cps);
15870         }
15871     }
15872 }
15873
15874 ChessProgramState *
15875 WhitePlayer ()
15876 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15877 {
15878     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15879        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15880         return &second;
15881     return &first;
15882 }
15883
15884 void
15885 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15886 {
15887     char message[MSG_SIZ];
15888     long time, otime;
15889
15890     /* Note: this routine must be called when the clocks are stopped
15891        or when they have *just* been set or switched; otherwise
15892        it will be off by the time since the current tick started.
15893     */
15894     if (machineWhite) {
15895         time = whiteTimeRemaining / 10;
15896         otime = blackTimeRemaining / 10;
15897     } else {
15898         time = blackTimeRemaining / 10;
15899         otime = whiteTimeRemaining / 10;
15900     }
15901     /* [HGM] translate opponent's time by time-odds factor */
15902     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15903
15904     if (time <= 0) time = 1;
15905     if (otime <= 0) otime = 1;
15906
15907     snprintf(message, MSG_SIZ, "time %ld\n", time);
15908     SendToProgram(message, cps);
15909
15910     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15911     SendToProgram(message, cps);
15912 }
15913
15914 int
15915 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15916 {
15917   char buf[MSG_SIZ];
15918   int len = strlen(name);
15919   int val;
15920
15921   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15922     (*p) += len + 1;
15923     sscanf(*p, "%d", &val);
15924     *loc = (val != 0);
15925     while (**p && **p != ' ')
15926       (*p)++;
15927     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15928     SendToProgram(buf, cps);
15929     return TRUE;
15930   }
15931   return FALSE;
15932 }
15933
15934 int
15935 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15936 {
15937   char buf[MSG_SIZ];
15938   int len = strlen(name);
15939   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15940     (*p) += len + 1;
15941     sscanf(*p, "%d", loc);
15942     while (**p && **p != ' ') (*p)++;
15943     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15944     SendToProgram(buf, cps);
15945     return TRUE;
15946   }
15947   return FALSE;
15948 }
15949
15950 int
15951 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
15952 {
15953   char buf[MSG_SIZ];
15954   int len = strlen(name);
15955   if (strncmp((*p), name, len) == 0
15956       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15957     (*p) += len + 2;
15958     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
15959     sscanf(*p, "%[^\"]", *loc);
15960     while (**p && **p != '\"') (*p)++;
15961     if (**p == '\"') (*p)++;
15962     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15963     SendToProgram(buf, cps);
15964     return TRUE;
15965   }
15966   return FALSE;
15967 }
15968
15969 int
15970 ParseOption (Option *opt, ChessProgramState *cps)
15971 // [HGM] options: process the string that defines an engine option, and determine
15972 // name, type, default value, and allowed value range
15973 {
15974         char *p, *q, buf[MSG_SIZ];
15975         int n, min = (-1)<<31, max = 1<<31, def;
15976
15977         if(p = strstr(opt->name, " -spin ")) {
15978             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15979             if(max < min) max = min; // enforce consistency
15980             if(def < min) def = min;
15981             if(def > max) def = max;
15982             opt->value = def;
15983             opt->min = min;
15984             opt->max = max;
15985             opt->type = Spin;
15986         } else if((p = strstr(opt->name, " -slider "))) {
15987             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15988             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15989             if(max < min) max = min; // enforce consistency
15990             if(def < min) def = min;
15991             if(def > max) def = max;
15992             opt->value = def;
15993             opt->min = min;
15994             opt->max = max;
15995             opt->type = Spin; // Slider;
15996         } else if((p = strstr(opt->name, " -string "))) {
15997             opt->textValue = p+9;
15998             opt->type = TextBox;
15999         } else if((p = strstr(opt->name, " -file "))) {
16000             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16001             opt->textValue = p+7;
16002             opt->type = FileName; // FileName;
16003         } else if((p = strstr(opt->name, " -path "))) {
16004             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16005             opt->textValue = p+7;
16006             opt->type = PathName; // PathName;
16007         } else if(p = strstr(opt->name, " -check ")) {
16008             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16009             opt->value = (def != 0);
16010             opt->type = CheckBox;
16011         } else if(p = strstr(opt->name, " -combo ")) {
16012             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16013             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16014             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16015             opt->value = n = 0;
16016             while(q = StrStr(q, " /// ")) {
16017                 n++; *q = 0;    // count choices, and null-terminate each of them
16018                 q += 5;
16019                 if(*q == '*') { // remember default, which is marked with * prefix
16020                     q++;
16021                     opt->value = n;
16022                 }
16023                 cps->comboList[cps->comboCnt++] = q;
16024             }
16025             cps->comboList[cps->comboCnt++] = NULL;
16026             opt->max = n + 1;
16027             opt->type = ComboBox;
16028         } else if(p = strstr(opt->name, " -button")) {
16029             opt->type = Button;
16030         } else if(p = strstr(opt->name, " -save")) {
16031             opt->type = SaveButton;
16032         } else return FALSE;
16033         *p = 0; // terminate option name
16034         // now look if the command-line options define a setting for this engine option.
16035         if(cps->optionSettings && cps->optionSettings[0])
16036             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16037         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16038           snprintf(buf, MSG_SIZ, "option %s", p);
16039                 if(p = strstr(buf, ",")) *p = 0;
16040                 if(q = strchr(buf, '=')) switch(opt->type) {
16041                     case ComboBox:
16042                         for(n=0; n<opt->max; n++)
16043                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16044                         break;
16045                     case TextBox:
16046                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16047                         break;
16048                     case Spin:
16049                     case CheckBox:
16050                         opt->value = atoi(q+1);
16051                     default:
16052                         break;
16053                 }
16054                 strcat(buf, "\n");
16055                 SendToProgram(buf, cps);
16056         }
16057         return TRUE;
16058 }
16059
16060 void
16061 FeatureDone (ChessProgramState *cps, int val)
16062 {
16063   DelayedEventCallback cb = GetDelayedEvent();
16064   if ((cb == InitBackEnd3 && cps == &first) ||
16065       (cb == SettingsMenuIfReady && cps == &second) ||
16066       (cb == LoadEngine) ||
16067       (cb == TwoMachinesEventIfReady)) {
16068     CancelDelayedEvent();
16069     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16070   }
16071   cps->initDone = val;
16072   if(val) cps->reload = FALSE;
16073 }
16074
16075 /* Parse feature command from engine */
16076 void
16077 ParseFeatures (char *args, ChessProgramState *cps)
16078 {
16079   char *p = args;
16080   char *q = NULL;
16081   int val;
16082   char buf[MSG_SIZ];
16083
16084   for (;;) {
16085     while (*p == ' ') p++;
16086     if (*p == NULLCHAR) return;
16087
16088     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16089     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16090     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16091     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16092     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16093     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16094     if (BoolFeature(&p, "reuse", &val, cps)) {
16095       /* Engine can disable reuse, but can't enable it if user said no */
16096       if (!val) cps->reuse = FALSE;
16097       continue;
16098     }
16099     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16100     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16101       if (gameMode == TwoMachinesPlay) {
16102         DisplayTwoMachinesTitle();
16103       } else {
16104         DisplayTitle("");
16105       }
16106       continue;
16107     }
16108     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16109     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16110     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16111     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16112     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16113     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16114     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16115     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16116     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16117     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16118     if (IntFeature(&p, "done", &val, cps)) {
16119       FeatureDone(cps, val);
16120       continue;
16121     }
16122     /* Added by Tord: */
16123     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16124     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16125     /* End of additions by Tord */
16126
16127     /* [HGM] added features: */
16128     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16129     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16130     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16131     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16132     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16133     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16134     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16135         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16136         FREE(cps->option[cps->nrOptions].name);
16137         cps->option[cps->nrOptions].name = q; q = NULL;
16138         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16139           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16140             SendToProgram(buf, cps);
16141             continue;
16142         }
16143         if(cps->nrOptions >= MAX_OPTIONS) {
16144             cps->nrOptions--;
16145             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16146             DisplayError(buf, 0);
16147         }
16148         continue;
16149     }
16150     /* End of additions by HGM */
16151
16152     /* unknown feature: complain and skip */
16153     q = p;
16154     while (*q && *q != '=') q++;
16155     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16156     SendToProgram(buf, cps);
16157     p = q;
16158     if (*p == '=') {
16159       p++;
16160       if (*p == '\"') {
16161         p++;
16162         while (*p && *p != '\"') p++;
16163         if (*p == '\"') p++;
16164       } else {
16165         while (*p && *p != ' ') p++;
16166       }
16167     }
16168   }
16169
16170 }
16171
16172 void
16173 PeriodicUpdatesEvent (int newState)
16174 {
16175     if (newState == appData.periodicUpdates)
16176       return;
16177
16178     appData.periodicUpdates=newState;
16179
16180     /* Display type changes, so update it now */
16181 //    DisplayAnalysis();
16182
16183     /* Get the ball rolling again... */
16184     if (newState) {
16185         AnalysisPeriodicEvent(1);
16186         StartAnalysisClock();
16187     }
16188 }
16189
16190 void
16191 PonderNextMoveEvent (int newState)
16192 {
16193     if (newState == appData.ponderNextMove) return;
16194     if (gameMode == EditPosition) EditPositionDone(TRUE);
16195     if (newState) {
16196         SendToProgram("hard\n", &first);
16197         if (gameMode == TwoMachinesPlay) {
16198             SendToProgram("hard\n", &second);
16199         }
16200     } else {
16201         SendToProgram("easy\n", &first);
16202         thinkOutput[0] = NULLCHAR;
16203         if (gameMode == TwoMachinesPlay) {
16204             SendToProgram("easy\n", &second);
16205         }
16206     }
16207     appData.ponderNextMove = newState;
16208 }
16209
16210 void
16211 NewSettingEvent (int option, int *feature, char *command, int value)
16212 {
16213     char buf[MSG_SIZ];
16214
16215     if (gameMode == EditPosition) EditPositionDone(TRUE);
16216     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16217     if(feature == NULL || *feature) SendToProgram(buf, &first);
16218     if (gameMode == TwoMachinesPlay) {
16219         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16220     }
16221 }
16222
16223 void
16224 ShowThinkingEvent ()
16225 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16226 {
16227     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16228     int newState = appData.showThinking
16229         // [HGM] thinking: other features now need thinking output as well
16230         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16231
16232     if (oldState == newState) return;
16233     oldState = newState;
16234     if (gameMode == EditPosition) EditPositionDone(TRUE);
16235     if (oldState) {
16236         SendToProgram("post\n", &first);
16237         if (gameMode == TwoMachinesPlay) {
16238             SendToProgram("post\n", &second);
16239         }
16240     } else {
16241         SendToProgram("nopost\n", &first);
16242         thinkOutput[0] = NULLCHAR;
16243         if (gameMode == TwoMachinesPlay) {
16244             SendToProgram("nopost\n", &second);
16245         }
16246     }
16247 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16248 }
16249
16250 void
16251 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16252 {
16253   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16254   if (pr == NoProc) return;
16255   AskQuestion(title, question, replyPrefix, pr);
16256 }
16257
16258 void
16259 TypeInEvent (char firstChar)
16260 {
16261     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16262         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16263         gameMode == AnalyzeMode || gameMode == EditGame ||
16264         gameMode == EditPosition || gameMode == IcsExamining ||
16265         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16266         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16267                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16268                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16269         gameMode == Training) PopUpMoveDialog(firstChar);
16270 }
16271
16272 void
16273 TypeInDoneEvent (char *move)
16274 {
16275         Board board;
16276         int n, fromX, fromY, toX, toY;
16277         char promoChar;
16278         ChessMove moveType;
16279
16280         // [HGM] FENedit
16281         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16282                 EditPositionPasteFEN(move);
16283                 return;
16284         }
16285         // [HGM] movenum: allow move number to be typed in any mode
16286         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16287           ToNrEvent(2*n-1);
16288           return;
16289         }
16290         // undocumented kludge: allow command-line option to be typed in!
16291         // (potentially fatal, and does not implement the effect of the option.)
16292         // should only be used for options that are values on which future decisions will be made,
16293         // and definitely not on options that would be used during initialization.
16294         if(strstr(move, "!!! -") == move) {
16295             ParseArgsFromString(move+4);
16296             return;
16297         }
16298
16299       if (gameMode != EditGame && currentMove != forwardMostMove &&
16300         gameMode != Training) {
16301         DisplayMoveError(_("Displayed move is not current"));
16302       } else {
16303         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16304           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16305         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16306         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16307           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16308           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16309         } else {
16310           DisplayMoveError(_("Could not parse move"));
16311         }
16312       }
16313 }
16314
16315 void
16316 DisplayMove (int moveNumber)
16317 {
16318     char message[MSG_SIZ];
16319     char res[MSG_SIZ];
16320     char cpThinkOutput[MSG_SIZ];
16321
16322     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16323
16324     if (moveNumber == forwardMostMove - 1 ||
16325         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16326
16327         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16328
16329         if (strchr(cpThinkOutput, '\n')) {
16330             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16331         }
16332     } else {
16333         *cpThinkOutput = NULLCHAR;
16334     }
16335
16336     /* [AS] Hide thinking from human user */
16337     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16338         *cpThinkOutput = NULLCHAR;
16339         if( thinkOutput[0] != NULLCHAR ) {
16340             int i;
16341
16342             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16343                 cpThinkOutput[i] = '.';
16344             }
16345             cpThinkOutput[i] = NULLCHAR;
16346             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16347         }
16348     }
16349
16350     if (moveNumber == forwardMostMove - 1 &&
16351         gameInfo.resultDetails != NULL) {
16352         if (gameInfo.resultDetails[0] == NULLCHAR) {
16353           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16354         } else {
16355           snprintf(res, MSG_SIZ, " {%s} %s",
16356                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16357         }
16358     } else {
16359         res[0] = NULLCHAR;
16360     }
16361
16362     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16363         DisplayMessage(res, cpThinkOutput);
16364     } else {
16365       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16366                 WhiteOnMove(moveNumber) ? " " : ".. ",
16367                 parseList[moveNumber], res);
16368         DisplayMessage(message, cpThinkOutput);
16369     }
16370 }
16371
16372 void
16373 DisplayComment (int moveNumber, char *text)
16374 {
16375     char title[MSG_SIZ];
16376
16377     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16378       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16379     } else {
16380       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16381               WhiteOnMove(moveNumber) ? " " : ".. ",
16382               parseList[moveNumber]);
16383     }
16384     if (text != NULL && (appData.autoDisplayComment || commentUp))
16385         CommentPopUp(title, text);
16386 }
16387
16388 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16389  * might be busy thinking or pondering.  It can be omitted if your
16390  * gnuchess is configured to stop thinking immediately on any user
16391  * input.  However, that gnuchess feature depends on the FIONREAD
16392  * ioctl, which does not work properly on some flavors of Unix.
16393  */
16394 void
16395 Attention (ChessProgramState *cps)
16396 {
16397 #if ATTENTION
16398     if (!cps->useSigint) return;
16399     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16400     switch (gameMode) {
16401       case MachinePlaysWhite:
16402       case MachinePlaysBlack:
16403       case TwoMachinesPlay:
16404       case IcsPlayingWhite:
16405       case IcsPlayingBlack:
16406       case AnalyzeMode:
16407       case AnalyzeFile:
16408         /* Skip if we know it isn't thinking */
16409         if (!cps->maybeThinking) return;
16410         if (appData.debugMode)
16411           fprintf(debugFP, "Interrupting %s\n", cps->which);
16412         InterruptChildProcess(cps->pr);
16413         cps->maybeThinking = FALSE;
16414         break;
16415       default:
16416         break;
16417     }
16418 #endif /*ATTENTION*/
16419 }
16420
16421 int
16422 CheckFlags ()
16423 {
16424     if (whiteTimeRemaining <= 0) {
16425         if (!whiteFlag) {
16426             whiteFlag = TRUE;
16427             if (appData.icsActive) {
16428                 if (appData.autoCallFlag &&
16429                     gameMode == IcsPlayingBlack && !blackFlag) {
16430                   SendToICS(ics_prefix);
16431                   SendToICS("flag\n");
16432                 }
16433             } else {
16434                 if (blackFlag) {
16435                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16436                 } else {
16437                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16438                     if (appData.autoCallFlag) {
16439                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16440                         return TRUE;
16441                     }
16442                 }
16443             }
16444         }
16445     }
16446     if (blackTimeRemaining <= 0) {
16447         if (!blackFlag) {
16448             blackFlag = TRUE;
16449             if (appData.icsActive) {
16450                 if (appData.autoCallFlag &&
16451                     gameMode == IcsPlayingWhite && !whiteFlag) {
16452                   SendToICS(ics_prefix);
16453                   SendToICS("flag\n");
16454                 }
16455             } else {
16456                 if (whiteFlag) {
16457                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16458                 } else {
16459                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16460                     if (appData.autoCallFlag) {
16461                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16462                         return TRUE;
16463                     }
16464                 }
16465             }
16466         }
16467     }
16468     return FALSE;
16469 }
16470
16471 void
16472 CheckTimeControl ()
16473 {
16474     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16475         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16476
16477     /*
16478      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16479      */
16480     if ( !WhiteOnMove(forwardMostMove) ) {
16481         /* White made time control */
16482         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16483         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16484         /* [HGM] time odds: correct new time quota for time odds! */
16485                                             / WhitePlayer()->timeOdds;
16486         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16487     } else {
16488         lastBlack -= blackTimeRemaining;
16489         /* Black made time control */
16490         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16491                                             / WhitePlayer()->other->timeOdds;
16492         lastWhite = whiteTimeRemaining;
16493     }
16494 }
16495
16496 void
16497 DisplayBothClocks ()
16498 {
16499     int wom = gameMode == EditPosition ?
16500       !blackPlaysFirst : WhiteOnMove(currentMove);
16501     DisplayWhiteClock(whiteTimeRemaining, wom);
16502     DisplayBlackClock(blackTimeRemaining, !wom);
16503 }
16504
16505
16506 /* Timekeeping seems to be a portability nightmare.  I think everyone
16507    has ftime(), but I'm really not sure, so I'm including some ifdefs
16508    to use other calls if you don't.  Clocks will be less accurate if
16509    you have neither ftime nor gettimeofday.
16510 */
16511
16512 /* VS 2008 requires the #include outside of the function */
16513 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16514 #include <sys/timeb.h>
16515 #endif
16516
16517 /* Get the current time as a TimeMark */
16518 void
16519 GetTimeMark (TimeMark *tm)
16520 {
16521 #if HAVE_GETTIMEOFDAY
16522
16523     struct timeval timeVal;
16524     struct timezone timeZone;
16525
16526     gettimeofday(&timeVal, &timeZone);
16527     tm->sec = (long) timeVal.tv_sec;
16528     tm->ms = (int) (timeVal.tv_usec / 1000L);
16529
16530 #else /*!HAVE_GETTIMEOFDAY*/
16531 #if HAVE_FTIME
16532
16533 // include <sys/timeb.h> / moved to just above start of function
16534     struct timeb timeB;
16535
16536     ftime(&timeB);
16537     tm->sec = (long) timeB.time;
16538     tm->ms = (int) timeB.millitm;
16539
16540 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16541     tm->sec = (long) time(NULL);
16542     tm->ms = 0;
16543 #endif
16544 #endif
16545 }
16546
16547 /* Return the difference in milliseconds between two
16548    time marks.  We assume the difference will fit in a long!
16549 */
16550 long
16551 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16552 {
16553     return 1000L*(tm2->sec - tm1->sec) +
16554            (long) (tm2->ms - tm1->ms);
16555 }
16556
16557
16558 /*
16559  * Code to manage the game clocks.
16560  *
16561  * In tournament play, black starts the clock and then white makes a move.
16562  * We give the human user a slight advantage if he is playing white---the
16563  * clocks don't run until he makes his first move, so it takes zero time.
16564  * Also, we don't account for network lag, so we could get out of sync
16565  * with GNU Chess's clock -- but then, referees are always right.
16566  */
16567
16568 static TimeMark tickStartTM;
16569 static long intendedTickLength;
16570
16571 long
16572 NextTickLength (long timeRemaining)
16573 {
16574     long nominalTickLength, nextTickLength;
16575
16576     if (timeRemaining > 0L && timeRemaining <= 10000L)
16577       nominalTickLength = 100L;
16578     else
16579       nominalTickLength = 1000L;
16580     nextTickLength = timeRemaining % nominalTickLength;
16581     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16582
16583     return nextTickLength;
16584 }
16585
16586 /* Adjust clock one minute up or down */
16587 void
16588 AdjustClock (Boolean which, int dir)
16589 {
16590     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16591     if(which) blackTimeRemaining += 60000*dir;
16592     else      whiteTimeRemaining += 60000*dir;
16593     DisplayBothClocks();
16594     adjustedClock = TRUE;
16595 }
16596
16597 /* Stop clocks and reset to a fresh time control */
16598 void
16599 ResetClocks ()
16600 {
16601     (void) StopClockTimer();
16602     if (appData.icsActive) {
16603         whiteTimeRemaining = blackTimeRemaining = 0;
16604     } else if (searchTime) {
16605         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16606         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16607     } else { /* [HGM] correct new time quote for time odds */
16608         whiteTC = blackTC = fullTimeControlString;
16609         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16610         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16611     }
16612     if (whiteFlag || blackFlag) {
16613         DisplayTitle("");
16614         whiteFlag = blackFlag = FALSE;
16615     }
16616     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16617     DisplayBothClocks();
16618     adjustedClock = FALSE;
16619 }
16620
16621 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16622
16623 /* Decrement running clock by amount of time that has passed */
16624 void
16625 DecrementClocks ()
16626 {
16627     long timeRemaining;
16628     long lastTickLength, fudge;
16629     TimeMark now;
16630
16631     if (!appData.clockMode) return;
16632     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16633
16634     GetTimeMark(&now);
16635
16636     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16637
16638     /* Fudge if we woke up a little too soon */
16639     fudge = intendedTickLength - lastTickLength;
16640     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16641
16642     if (WhiteOnMove(forwardMostMove)) {
16643         if(whiteNPS >= 0) lastTickLength = 0;
16644         timeRemaining = whiteTimeRemaining -= lastTickLength;
16645         if(timeRemaining < 0 && !appData.icsActive) {
16646             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16647             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16648                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16649                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16650             }
16651         }
16652         DisplayWhiteClock(whiteTimeRemaining - fudge,
16653                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16654     } else {
16655         if(blackNPS >= 0) lastTickLength = 0;
16656         timeRemaining = blackTimeRemaining -= lastTickLength;
16657         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16658             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16659             if(suddenDeath) {
16660                 blackStartMove = forwardMostMove;
16661                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16662             }
16663         }
16664         DisplayBlackClock(blackTimeRemaining - fudge,
16665                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16666     }
16667     if (CheckFlags()) return;
16668
16669     if(twoBoards) { // count down secondary board's clocks as well
16670         activePartnerTime -= lastTickLength;
16671         partnerUp = 1;
16672         if(activePartner == 'W')
16673             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16674         else
16675             DisplayBlackClock(activePartnerTime, TRUE);
16676         partnerUp = 0;
16677     }
16678
16679     tickStartTM = now;
16680     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16681     StartClockTimer(intendedTickLength);
16682
16683     /* if the time remaining has fallen below the alarm threshold, sound the
16684      * alarm. if the alarm has sounded and (due to a takeback or time control
16685      * with increment) the time remaining has increased to a level above the
16686      * threshold, reset the alarm so it can sound again.
16687      */
16688
16689     if (appData.icsActive && appData.icsAlarm) {
16690
16691         /* make sure we are dealing with the user's clock */
16692         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16693                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16694            )) return;
16695
16696         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16697             alarmSounded = FALSE;
16698         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16699             PlayAlarmSound();
16700             alarmSounded = TRUE;
16701         }
16702     }
16703 }
16704
16705
16706 /* A player has just moved, so stop the previously running
16707    clock and (if in clock mode) start the other one.
16708    We redisplay both clocks in case we're in ICS mode, because
16709    ICS gives us an update to both clocks after every move.
16710    Note that this routine is called *after* forwardMostMove
16711    is updated, so the last fractional tick must be subtracted
16712    from the color that is *not* on move now.
16713 */
16714 void
16715 SwitchClocks (int newMoveNr)
16716 {
16717     long lastTickLength;
16718     TimeMark now;
16719     int flagged = FALSE;
16720
16721     GetTimeMark(&now);
16722
16723     if (StopClockTimer() && appData.clockMode) {
16724         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16725         if (!WhiteOnMove(forwardMostMove)) {
16726             if(blackNPS >= 0) lastTickLength = 0;
16727             blackTimeRemaining -= lastTickLength;
16728            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16729 //         if(pvInfoList[forwardMostMove].time == -1)
16730                  pvInfoList[forwardMostMove].time =               // use GUI time
16731                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16732         } else {
16733            if(whiteNPS >= 0) lastTickLength = 0;
16734            whiteTimeRemaining -= lastTickLength;
16735            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16736 //         if(pvInfoList[forwardMostMove].time == -1)
16737                  pvInfoList[forwardMostMove].time =
16738                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16739         }
16740         flagged = CheckFlags();
16741     }
16742     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16743     CheckTimeControl();
16744
16745     if (flagged || !appData.clockMode) return;
16746
16747     switch (gameMode) {
16748       case MachinePlaysBlack:
16749       case MachinePlaysWhite:
16750       case BeginningOfGame:
16751         if (pausing) return;
16752         break;
16753
16754       case EditGame:
16755       case PlayFromGameFile:
16756       case IcsExamining:
16757         return;
16758
16759       default:
16760         break;
16761     }
16762
16763     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16764         if(WhiteOnMove(forwardMostMove))
16765              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16766         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16767     }
16768
16769     tickStartTM = now;
16770     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16771       whiteTimeRemaining : blackTimeRemaining);
16772     StartClockTimer(intendedTickLength);
16773 }
16774
16775
16776 /* Stop both clocks */
16777 void
16778 StopClocks ()
16779 {
16780     long lastTickLength;
16781     TimeMark now;
16782
16783     if (!StopClockTimer()) return;
16784     if (!appData.clockMode) return;
16785
16786     GetTimeMark(&now);
16787
16788     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16789     if (WhiteOnMove(forwardMostMove)) {
16790         if(whiteNPS >= 0) lastTickLength = 0;
16791         whiteTimeRemaining -= lastTickLength;
16792         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16793     } else {
16794         if(blackNPS >= 0) lastTickLength = 0;
16795         blackTimeRemaining -= lastTickLength;
16796         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16797     }
16798     CheckFlags();
16799 }
16800
16801 /* Start clock of player on move.  Time may have been reset, so
16802    if clock is already running, stop and restart it. */
16803 void
16804 StartClocks ()
16805 {
16806     (void) StopClockTimer(); /* in case it was running already */
16807     DisplayBothClocks();
16808     if (CheckFlags()) return;
16809
16810     if (!appData.clockMode) return;
16811     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16812
16813     GetTimeMark(&tickStartTM);
16814     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16815       whiteTimeRemaining : blackTimeRemaining);
16816
16817    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16818     whiteNPS = blackNPS = -1;
16819     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16820        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16821         whiteNPS = first.nps;
16822     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16823        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16824         blackNPS = first.nps;
16825     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16826         whiteNPS = second.nps;
16827     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16828         blackNPS = second.nps;
16829     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16830
16831     StartClockTimer(intendedTickLength);
16832 }
16833
16834 char *
16835 TimeString (long ms)
16836 {
16837     long second, minute, hour, day;
16838     char *sign = "";
16839     static char buf[32];
16840
16841     if (ms > 0 && ms <= 9900) {
16842       /* convert milliseconds to tenths, rounding up */
16843       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16844
16845       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16846       return buf;
16847     }
16848
16849     /* convert milliseconds to seconds, rounding up */
16850     /* use floating point to avoid strangeness of integer division
16851        with negative dividends on many machines */
16852     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16853
16854     if (second < 0) {
16855         sign = "-";
16856         second = -second;
16857     }
16858
16859     day = second / (60 * 60 * 24);
16860     second = second % (60 * 60 * 24);
16861     hour = second / (60 * 60);
16862     second = second % (60 * 60);
16863     minute = second / 60;
16864     second = second % 60;
16865
16866     if (day > 0)
16867       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16868               sign, day, hour, minute, second);
16869     else if (hour > 0)
16870       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16871     else
16872       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16873
16874     return buf;
16875 }
16876
16877
16878 /*
16879  * This is necessary because some C libraries aren't ANSI C compliant yet.
16880  */
16881 char *
16882 StrStr (char *string, char *match)
16883 {
16884     int i, length;
16885
16886     length = strlen(match);
16887
16888     for (i = strlen(string) - length; i >= 0; i--, string++)
16889       if (!strncmp(match, string, length))
16890         return string;
16891
16892     return NULL;
16893 }
16894
16895 char *
16896 StrCaseStr (char *string, char *match)
16897 {
16898     int i, j, length;
16899
16900     length = strlen(match);
16901
16902     for (i = strlen(string) - length; i >= 0; i--, string++) {
16903         for (j = 0; j < length; j++) {
16904             if (ToLower(match[j]) != ToLower(string[j]))
16905               break;
16906         }
16907         if (j == length) return string;
16908     }
16909
16910     return NULL;
16911 }
16912
16913 #ifndef _amigados
16914 int
16915 StrCaseCmp (char *s1, char *s2)
16916 {
16917     char c1, c2;
16918
16919     for (;;) {
16920         c1 = ToLower(*s1++);
16921         c2 = ToLower(*s2++);
16922         if (c1 > c2) return 1;
16923         if (c1 < c2) return -1;
16924         if (c1 == NULLCHAR) return 0;
16925     }
16926 }
16927
16928
16929 int
16930 ToLower (int c)
16931 {
16932     return isupper(c) ? tolower(c) : c;
16933 }
16934
16935
16936 int
16937 ToUpper (int c)
16938 {
16939     return islower(c) ? toupper(c) : c;
16940 }
16941 #endif /* !_amigados    */
16942
16943 char *
16944 StrSave (char *s)
16945 {
16946   char *ret;
16947
16948   if ((ret = (char *) malloc(strlen(s) + 1)))
16949     {
16950       safeStrCpy(ret, s, strlen(s)+1);
16951     }
16952   return ret;
16953 }
16954
16955 char *
16956 StrSavePtr (char *s, char **savePtr)
16957 {
16958     if (*savePtr) {
16959         free(*savePtr);
16960     }
16961     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16962       safeStrCpy(*savePtr, s, strlen(s)+1);
16963     }
16964     return(*savePtr);
16965 }
16966
16967 char *
16968 PGNDate ()
16969 {
16970     time_t clock;
16971     struct tm *tm;
16972     char buf[MSG_SIZ];
16973
16974     clock = time((time_t *)NULL);
16975     tm = localtime(&clock);
16976     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16977             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16978     return StrSave(buf);
16979 }
16980
16981
16982 char *
16983 PositionToFEN (int move, char *overrideCastling)
16984 {
16985     int i, j, fromX, fromY, toX, toY;
16986     int whiteToPlay;
16987     char buf[MSG_SIZ];
16988     char *p, *q;
16989     int emptycount;
16990     ChessSquare piece;
16991
16992     whiteToPlay = (gameMode == EditPosition) ?
16993       !blackPlaysFirst : (move % 2 == 0);
16994     p = buf;
16995
16996     /* Piece placement data */
16997     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16998         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16999         emptycount = 0;
17000         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17001             if (boards[move][i][j] == EmptySquare) {
17002                 emptycount++;
17003             } else { ChessSquare piece = boards[move][i][j];
17004                 if (emptycount > 0) {
17005                     if(emptycount<10) /* [HGM] can be >= 10 */
17006                         *p++ = '0' + emptycount;
17007                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17008                     emptycount = 0;
17009                 }
17010                 if(PieceToChar(piece) == '+') {
17011                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17012                     *p++ = '+';
17013                     piece = (ChessSquare)(DEMOTED piece);
17014                 }
17015                 *p++ = PieceToChar(piece);
17016                 if(p[-1] == '~') {
17017                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17018                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17019                     *p++ = '~';
17020                 }
17021             }
17022         }
17023         if (emptycount > 0) {
17024             if(emptycount<10) /* [HGM] can be >= 10 */
17025                 *p++ = '0' + emptycount;
17026             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17027             emptycount = 0;
17028         }
17029         *p++ = '/';
17030     }
17031     *(p - 1) = ' ';
17032
17033     /* [HGM] print Crazyhouse or Shogi holdings */
17034     if( gameInfo.holdingsWidth ) {
17035         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17036         q = p;
17037         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17038             piece = boards[move][i][BOARD_WIDTH-1];
17039             if( piece != EmptySquare )
17040               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17041                   *p++ = PieceToChar(piece);
17042         }
17043         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17044             piece = boards[move][BOARD_HEIGHT-i-1][0];
17045             if( piece != EmptySquare )
17046               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17047                   *p++ = PieceToChar(piece);
17048         }
17049
17050         if( q == p ) *p++ = '-';
17051         *p++ = ']';
17052         *p++ = ' ';
17053     }
17054
17055     /* Active color */
17056     *p++ = whiteToPlay ? 'w' : 'b';
17057     *p++ = ' ';
17058
17059   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17060     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17061   } else {
17062   if(nrCastlingRights) {
17063      q = p;
17064      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17065        /* [HGM] write directly from rights */
17066            if(boards[move][CASTLING][2] != NoRights &&
17067               boards[move][CASTLING][0] != NoRights   )
17068                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17069            if(boards[move][CASTLING][2] != NoRights &&
17070               boards[move][CASTLING][1] != NoRights   )
17071                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17072            if(boards[move][CASTLING][5] != NoRights &&
17073               boards[move][CASTLING][3] != NoRights   )
17074                 *p++ = boards[move][CASTLING][3] + AAA;
17075            if(boards[move][CASTLING][5] != NoRights &&
17076               boards[move][CASTLING][4] != NoRights   )
17077                 *p++ = boards[move][CASTLING][4] + AAA;
17078      } else {
17079
17080         /* [HGM] write true castling rights */
17081         if( nrCastlingRights == 6 ) {
17082             int q, k=0;
17083             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17084                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17085             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17086                  boards[move][CASTLING][2] != NoRights  );
17087             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17088                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17089                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17090                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17091                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17092             }
17093             if(q) *p++ = 'Q';
17094             k = 0;
17095             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17096                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17097             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17098                  boards[move][CASTLING][5] != NoRights  );
17099             if(gameInfo.variant == VariantSChess) {
17100                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17101                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17102                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17103                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17104             }
17105             if(q) *p++ = 'q';
17106         }
17107      }
17108      if (q == p) *p++ = '-'; /* No castling rights */
17109      *p++ = ' ';
17110   }
17111
17112   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17113      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17114     /* En passant target square */
17115     if (move > backwardMostMove) {
17116         fromX = moveList[move - 1][0] - AAA;
17117         fromY = moveList[move - 1][1] - ONE;
17118         toX = moveList[move - 1][2] - AAA;
17119         toY = moveList[move - 1][3] - ONE;
17120         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17121             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17122             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17123             fromX == toX) {
17124             /* 2-square pawn move just happened */
17125             *p++ = toX + AAA;
17126             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17127         } else {
17128             *p++ = '-';
17129         }
17130     } else if(move == backwardMostMove) {
17131         // [HGM] perhaps we should always do it like this, and forget the above?
17132         if((signed char)boards[move][EP_STATUS] >= 0) {
17133             *p++ = boards[move][EP_STATUS] + AAA;
17134             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17135         } else {
17136             *p++ = '-';
17137         }
17138     } else {
17139         *p++ = '-';
17140     }
17141     *p++ = ' ';
17142   }
17143   }
17144
17145     /* [HGM] find reversible plies */
17146     {   int i = 0, j=move;
17147
17148         if (appData.debugMode) { int k;
17149             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17150             for(k=backwardMostMove; k<=forwardMostMove; k++)
17151                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17152
17153         }
17154
17155         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17156         if( j == backwardMostMove ) i += initialRulePlies;
17157         sprintf(p, "%d ", i);
17158         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17159     }
17160     /* Fullmove number */
17161     sprintf(p, "%d", (move / 2) + 1);
17162
17163     return StrSave(buf);
17164 }
17165
17166 Boolean
17167 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17168 {
17169     int i, j;
17170     char *p, c;
17171     int emptycount, virgin[BOARD_FILES];
17172     ChessSquare piece;
17173
17174     p = fen;
17175
17176     /* [HGM] by default clear Crazyhouse holdings, if present */
17177     if(gameInfo.holdingsWidth) {
17178        for(i=0; i<BOARD_HEIGHT; i++) {
17179            board[i][0]             = EmptySquare; /* black holdings */
17180            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17181            board[i][1]             = (ChessSquare) 0; /* black counts */
17182            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17183        }
17184     }
17185
17186     /* Piece placement data */
17187     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17188         j = 0;
17189         for (;;) {
17190             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17191                 if (*p == '/') p++;
17192                 emptycount = gameInfo.boardWidth - j;
17193                 while (emptycount--)
17194                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17195                 break;
17196 #if(BOARD_FILES >= 10)
17197             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17198                 p++; emptycount=10;
17199                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17200                 while (emptycount--)
17201                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17202 #endif
17203             } else if (isdigit(*p)) {
17204                 emptycount = *p++ - '0';
17205                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17206                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17207                 while (emptycount--)
17208                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17209             } else if (*p == '+' || isalpha(*p)) {
17210                 if (j >= gameInfo.boardWidth) return FALSE;
17211                 if(*p=='+') {
17212                     piece = CharToPiece(*++p);
17213                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17214                     piece = (ChessSquare) (PROMOTED piece ); p++;
17215                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17216                 } else piece = CharToPiece(*p++);
17217
17218                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17219                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17220                     piece = (ChessSquare) (PROMOTED piece);
17221                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17222                     p++;
17223                 }
17224                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17225             } else {
17226                 return FALSE;
17227             }
17228         }
17229     }
17230     while (*p == '/' || *p == ' ') p++;
17231
17232     /* [HGM] look for Crazyhouse holdings here */
17233     while(*p==' ') p++;
17234     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17235         if(*p == '[') p++;
17236         if(*p == '-' ) p++; /* empty holdings */ else {
17237             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17238             /* if we would allow FEN reading to set board size, we would   */
17239             /* have to add holdings and shift the board read so far here   */
17240             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17241                 p++;
17242                 if((int) piece >= (int) BlackPawn ) {
17243                     i = (int)piece - (int)BlackPawn;
17244                     i = PieceToNumber((ChessSquare)i);
17245                     if( i >= gameInfo.holdingsSize ) return FALSE;
17246                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17247                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17248                 } else {
17249                     i = (int)piece - (int)WhitePawn;
17250                     i = PieceToNumber((ChessSquare)i);
17251                     if( i >= gameInfo.holdingsSize ) return FALSE;
17252                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17253                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17254                 }
17255             }
17256         }
17257         if(*p == ']') p++;
17258     }
17259
17260     while(*p == ' ') p++;
17261
17262     /* Active color */
17263     c = *p++;
17264     if(appData.colorNickNames) {
17265       if( c == appData.colorNickNames[0] ) c = 'w'; else
17266       if( c == appData.colorNickNames[1] ) c = 'b';
17267     }
17268     switch (c) {
17269       case 'w':
17270         *blackPlaysFirst = FALSE;
17271         break;
17272       case 'b':
17273         *blackPlaysFirst = TRUE;
17274         break;
17275       default:
17276         return FALSE;
17277     }
17278
17279     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17280     /* return the extra info in global variiables             */
17281
17282     /* set defaults in case FEN is incomplete */
17283     board[EP_STATUS] = EP_UNKNOWN;
17284     for(i=0; i<nrCastlingRights; i++ ) {
17285         board[CASTLING][i] =
17286             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17287     }   /* assume possible unless obviously impossible */
17288     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17289     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17290     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17291                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17292     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17293     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17294     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17295                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17296     FENrulePlies = 0;
17297
17298     while(*p==' ') p++;
17299     if(nrCastlingRights) {
17300       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17301       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17302           /* castling indicator present, so default becomes no castlings */
17303           for(i=0; i<nrCastlingRights; i++ ) {
17304                  board[CASTLING][i] = NoRights;
17305           }
17306       }
17307       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17308              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17309              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17310              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17311         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17312
17313         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17314             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17315             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17316         }
17317         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17318             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17319         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17320                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17321         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17322                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17323         switch(c) {
17324           case'K':
17325               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17326               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17327               board[CASTLING][2] = whiteKingFile;
17328               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17329               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17330               break;
17331           case'Q':
17332               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17333               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17334               board[CASTLING][2] = whiteKingFile;
17335               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17336               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17337               break;
17338           case'k':
17339               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17340               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17341               board[CASTLING][5] = blackKingFile;
17342               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17343               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17344               break;
17345           case'q':
17346               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17347               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17348               board[CASTLING][5] = blackKingFile;
17349               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17350               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17351           case '-':
17352               break;
17353           default: /* FRC castlings */
17354               if(c >= 'a') { /* black rights */
17355                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17356                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17357                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17358                   if(i == BOARD_RGHT) break;
17359                   board[CASTLING][5] = i;
17360                   c -= AAA;
17361                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17362                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17363                   if(c > i)
17364                       board[CASTLING][3] = c;
17365                   else
17366                       board[CASTLING][4] = c;
17367               } else { /* white rights */
17368                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17369                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17370                     if(board[0][i] == WhiteKing) break;
17371                   if(i == BOARD_RGHT) break;
17372                   board[CASTLING][2] = i;
17373                   c -= AAA - 'a' + 'A';
17374                   if(board[0][c] >= WhiteKing) break;
17375                   if(c > i)
17376                       board[CASTLING][0] = c;
17377                   else
17378                       board[CASTLING][1] = c;
17379               }
17380         }
17381       }
17382       for(i=0; i<nrCastlingRights; i++)
17383         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17384       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17385     if (appData.debugMode) {
17386         fprintf(debugFP, "FEN castling rights:");
17387         for(i=0; i<nrCastlingRights; i++)
17388         fprintf(debugFP, " %d", board[CASTLING][i]);
17389         fprintf(debugFP, "\n");
17390     }
17391
17392       while(*p==' ') p++;
17393     }
17394
17395     /* read e.p. field in games that know e.p. capture */
17396     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17397        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17398       if(*p=='-') {
17399         p++; board[EP_STATUS] = EP_NONE;
17400       } else {
17401          char c = *p++ - AAA;
17402
17403          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17404          if(*p >= '0' && *p <='9') p++;
17405          board[EP_STATUS] = c;
17406       }
17407     }
17408
17409
17410     if(sscanf(p, "%d", &i) == 1) {
17411         FENrulePlies = i; /* 50-move ply counter */
17412         /* (The move number is still ignored)    */
17413     }
17414
17415     return TRUE;
17416 }
17417
17418 void
17419 EditPositionPasteFEN (char *fen)
17420 {
17421   if (fen != NULL) {
17422     Board initial_position;
17423
17424     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17425       DisplayError(_("Bad FEN position in clipboard"), 0);
17426       return ;
17427     } else {
17428       int savedBlackPlaysFirst = blackPlaysFirst;
17429       EditPositionEvent();
17430       blackPlaysFirst = savedBlackPlaysFirst;
17431       CopyBoard(boards[0], initial_position);
17432       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17433       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17434       DisplayBothClocks();
17435       DrawPosition(FALSE, boards[currentMove]);
17436     }
17437   }
17438 }
17439
17440 static char cseq[12] = "\\   ";
17441
17442 Boolean
17443 set_cont_sequence (char *new_seq)
17444 {
17445     int len;
17446     Boolean ret;
17447
17448     // handle bad attempts to set the sequence
17449         if (!new_seq)
17450                 return 0; // acceptable error - no debug
17451
17452     len = strlen(new_seq);
17453     ret = (len > 0) && (len < sizeof(cseq));
17454     if (ret)
17455       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17456     else if (appData.debugMode)
17457       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17458     return ret;
17459 }
17460
17461 /*
17462     reformat a source message so words don't cross the width boundary.  internal
17463     newlines are not removed.  returns the wrapped size (no null character unless
17464     included in source message).  If dest is NULL, only calculate the size required
17465     for the dest buffer.  lp argument indicats line position upon entry, and it's
17466     passed back upon exit.
17467 */
17468 int
17469 wrap (char *dest, char *src, int count, int width, int *lp)
17470 {
17471     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17472
17473     cseq_len = strlen(cseq);
17474     old_line = line = *lp;
17475     ansi = len = clen = 0;
17476
17477     for (i=0; i < count; i++)
17478     {
17479         if (src[i] == '\033')
17480             ansi = 1;
17481
17482         // if we hit the width, back up
17483         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17484         {
17485             // store i & len in case the word is too long
17486             old_i = i, old_len = len;
17487
17488             // find the end of the last word
17489             while (i && src[i] != ' ' && src[i] != '\n')
17490             {
17491                 i--;
17492                 len--;
17493             }
17494
17495             // word too long?  restore i & len before splitting it
17496             if ((old_i-i+clen) >= width)
17497             {
17498                 i = old_i;
17499                 len = old_len;
17500             }
17501
17502             // extra space?
17503             if (i && src[i-1] == ' ')
17504                 len--;
17505
17506             if (src[i] != ' ' && src[i] != '\n')
17507             {
17508                 i--;
17509                 if (len)
17510                     len--;
17511             }
17512
17513             // now append the newline and continuation sequence
17514             if (dest)
17515                 dest[len] = '\n';
17516             len++;
17517             if (dest)
17518                 strncpy(dest+len, cseq, cseq_len);
17519             len += cseq_len;
17520             line = cseq_len;
17521             clen = cseq_len;
17522             continue;
17523         }
17524
17525         if (dest)
17526             dest[len] = src[i];
17527         len++;
17528         if (!ansi)
17529             line++;
17530         if (src[i] == '\n')
17531             line = 0;
17532         if (src[i] == 'm')
17533             ansi = 0;
17534     }
17535     if (dest && appData.debugMode)
17536     {
17537         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17538             count, width, line, len, *lp);
17539         show_bytes(debugFP, src, count);
17540         fprintf(debugFP, "\ndest: ");
17541         show_bytes(debugFP, dest, len);
17542         fprintf(debugFP, "\n");
17543     }
17544     *lp = dest ? line : old_line;
17545
17546     return len;
17547 }
17548
17549 // [HGM] vari: routines for shelving variations
17550 Boolean modeRestore = FALSE;
17551
17552 void
17553 PushInner (int firstMove, int lastMove)
17554 {
17555         int i, j, nrMoves = lastMove - firstMove;
17556
17557         // push current tail of game on stack
17558         savedResult[storedGames] = gameInfo.result;
17559         savedDetails[storedGames] = gameInfo.resultDetails;
17560         gameInfo.resultDetails = NULL;
17561         savedFirst[storedGames] = firstMove;
17562         savedLast [storedGames] = lastMove;
17563         savedFramePtr[storedGames] = framePtr;
17564         framePtr -= nrMoves; // reserve space for the boards
17565         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17566             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17567             for(j=0; j<MOVE_LEN; j++)
17568                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17569             for(j=0; j<2*MOVE_LEN; j++)
17570                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17571             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17572             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17573             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17574             pvInfoList[firstMove+i-1].depth = 0;
17575             commentList[framePtr+i] = commentList[firstMove+i];
17576             commentList[firstMove+i] = NULL;
17577         }
17578
17579         storedGames++;
17580         forwardMostMove = firstMove; // truncate game so we can start variation
17581 }
17582
17583 void
17584 PushTail (int firstMove, int lastMove)
17585 {
17586         if(appData.icsActive) { // only in local mode
17587                 forwardMostMove = currentMove; // mimic old ICS behavior
17588                 return;
17589         }
17590         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17591
17592         PushInner(firstMove, lastMove);
17593         if(storedGames == 1) GreyRevert(FALSE);
17594         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17595 }
17596
17597 void
17598 PopInner (Boolean annotate)
17599 {
17600         int i, j, nrMoves;
17601         char buf[8000], moveBuf[20];
17602
17603         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17604         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17605         nrMoves = savedLast[storedGames] - currentMove;
17606         if(annotate) {
17607                 int cnt = 10;
17608                 if(!WhiteOnMove(currentMove))
17609                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17610                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17611                 for(i=currentMove; i<forwardMostMove; i++) {
17612                         if(WhiteOnMove(i))
17613                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17614                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17615                         strcat(buf, moveBuf);
17616                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17617                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17618                 }
17619                 strcat(buf, ")");
17620         }
17621         for(i=1; i<=nrMoves; i++) { // copy last variation back
17622             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17623             for(j=0; j<MOVE_LEN; j++)
17624                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17625             for(j=0; j<2*MOVE_LEN; j++)
17626                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17627             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17628             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17629             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17630             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17631             commentList[currentMove+i] = commentList[framePtr+i];
17632             commentList[framePtr+i] = NULL;
17633         }
17634         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17635         framePtr = savedFramePtr[storedGames];
17636         gameInfo.result = savedResult[storedGames];
17637         if(gameInfo.resultDetails != NULL) {
17638             free(gameInfo.resultDetails);
17639       }
17640         gameInfo.resultDetails = savedDetails[storedGames];
17641         forwardMostMove = currentMove + nrMoves;
17642 }
17643
17644 Boolean
17645 PopTail (Boolean annotate)
17646 {
17647         if(appData.icsActive) return FALSE; // only in local mode
17648         if(!storedGames) return FALSE; // sanity
17649         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17650
17651         PopInner(annotate);
17652         if(currentMove < forwardMostMove) ForwardEvent(); else
17653         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17654
17655         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17656         return TRUE;
17657 }
17658
17659 void
17660 CleanupTail ()
17661 {       // remove all shelved variations
17662         int i;
17663         for(i=0; i<storedGames; i++) {
17664             if(savedDetails[i])
17665                 free(savedDetails[i]);
17666             savedDetails[i] = NULL;
17667         }
17668         for(i=framePtr; i<MAX_MOVES; i++) {
17669                 if(commentList[i]) free(commentList[i]);
17670                 commentList[i] = NULL;
17671         }
17672         framePtr = MAX_MOVES-1;
17673         storedGames = 0;
17674 }
17675
17676 void
17677 LoadVariation (int index, char *text)
17678 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17679         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17680         int level = 0, move;
17681
17682         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17683         // first find outermost bracketing variation
17684         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17685             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17686                 if(*p == '{') wait = '}'; else
17687                 if(*p == '[') wait = ']'; else
17688                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17689                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17690             }
17691             if(*p == wait) wait = NULLCHAR; // closing ]} found
17692             p++;
17693         }
17694         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17695         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17696         end[1] = NULLCHAR; // clip off comment beyond variation
17697         ToNrEvent(currentMove-1);
17698         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17699         // kludge: use ParsePV() to append variation to game
17700         move = currentMove;
17701         ParsePV(start, TRUE, TRUE);
17702         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17703         ClearPremoveHighlights();
17704         CommentPopDown();
17705         ToNrEvent(currentMove+1);
17706 }
17707
17708 void
17709 LoadTheme ()
17710 {
17711     char *p, *q, buf[MSG_SIZ];
17712     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17713         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17714         ParseArgsFromString(buf);
17715         ActivateTheme(TRUE); // also redo colors
17716         return;
17717     }
17718     p = nickName;
17719     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17720     {
17721         int len;
17722         q = appData.themeNames;
17723         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17724       if(appData.useBitmaps) {
17725         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17726                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17727                 appData.liteBackTextureMode,
17728                 appData.darkBackTextureMode );
17729       } else {
17730         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17731                 Col2Text(2),   // lightSquareColor
17732                 Col2Text(3) ); // darkSquareColor
17733       }
17734       if(appData.useBorder) {
17735         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17736                 appData.border);
17737       } else {
17738         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17739       }
17740       if(appData.useFont) {
17741         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17742                 appData.renderPiecesWithFont,
17743                 appData.fontToPieceTable,
17744                 Col2Text(9),    // appData.fontBackColorWhite
17745                 Col2Text(10) ); // appData.fontForeColorBlack
17746       } else {
17747         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17748                 appData.pieceDirectory);
17749         if(!appData.pieceDirectory[0])
17750           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17751                 Col2Text(0),   // whitePieceColor
17752                 Col2Text(1) ); // blackPieceColor
17753       }
17754       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17755                 Col2Text(4),   // highlightSquareColor
17756                 Col2Text(5) ); // premoveHighlightColor
17757         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17758         if(insert != q) insert[-1] = NULLCHAR;
17759         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17760         if(q)   free(q);
17761     }
17762     ActivateTheme(FALSE);
17763 }