Adapt WinBoard front-end to Mighty Lion
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278
279 /* States for ics_getting_history */
280 #define H_FALSE 0
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
286
287 /* whosays values for GameEnds */
288 #define GE_ICS 0
289 #define GE_ENGINE 1
290 #define GE_PLAYER 2
291 #define GE_FILE 3
292 #define GE_XBOARD 4
293 #define GE_ENGINE1 5
294 #define GE_ENGINE2 6
295
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
298
299 /* Different types of move when calling RegisterMove */
300 #define CMAIL_MOVE   0
301 #define CMAIL_RESIGN 1
302 #define CMAIL_DRAW   2
303 #define CMAIL_ACCEPT 3
304
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
309
310 /* Telnet protocol constants */
311 #define TN_WILL 0373
312 #define TN_WONT 0374
313 #define TN_DO   0375
314 #define TN_DONT 0376
315 #define TN_IAC  0377
316 #define TN_ECHO 0001
317 #define TN_SGA  0003
318 #define TN_PORT 23
319
320 char*
321 safeStrCpy (char *dst, const char *src, size_t count)
322 { // [HGM] made safe
323   int i;
324   assert( dst != NULL );
325   assert( src != NULL );
326   assert( count > 0 );
327
328   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329   if(  i == count && dst[count-1] != NULLCHAR)
330     {
331       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332       if(appData.debugMode)
333         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334     }
335
336   return dst;
337 }
338
339 /* Some compiler can't cast u64 to double
340  * This function do the job for us:
341
342  * We use the highest bit for cast, this only
343  * works if the highest bit is not
344  * in use (This should not happen)
345  *
346  * We used this for all compiler
347  */
348 double
349 u64ToDouble (u64 value)
350 {
351   double r;
352   u64 tmp = value & u64Const(0x7fffffffffffffff);
353   r = (double)(s64)tmp;
354   if (value & u64Const(0x8000000000000000))
355        r +=  9.2233720368547758080e18; /* 2^63 */
356  return r;
357 }
358
359 /* Fake up flags for now, as we aren't keeping track of castling
360    availability yet. [HGM] Change of logic: the flag now only
361    indicates the type of castlings allowed by the rule of the game.
362    The actual rights themselves are maintained in the array
363    castlingRights, as part of the game history, and are not probed
364    by this function.
365  */
366 int
367 PosFlags (index)
368 {
369   int flags = F_ALL_CASTLE_OK;
370   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371   switch (gameInfo.variant) {
372   case VariantSuicide:
373     flags &= ~F_ALL_CASTLE_OK;
374   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375     flags |= F_IGNORE_CHECK;
376   case VariantLosers:
377     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378     break;
379   case VariantAtomic:
380     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381     break;
382   case VariantKriegspiel:
383     flags |= F_KRIEGSPIEL_CAPTURE;
384     break;
385   case VariantCapaRandom:
386   case VariantFischeRandom:
387     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388   case VariantNoCastle:
389   case VariantShatranj:
390   case VariantCourier:
391   case VariantMakruk:
392   case VariantASEAN:
393   case VariantGrand:
394     flags &= ~F_ALL_CASTLE_OK;
395     break;
396   default:
397     break;
398   }
399   return flags;
400 }
401
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackMan, BlackFerz,
566         BlackKing, BlackMan, BlackKnight, BlackRook }
567 };
568
569 ChessSquare  lionArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
571         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackLion, BlackBishop, BlackQueen,
573         BlackKing, BlackBishop, BlackKnight, BlackRook }
574 };
575
576
577 #if (BOARD_FILES>=10)
578 ChessSquare ShogiArray[2][BOARD_FILES] = {
579     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
580         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
581     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
582         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
583 };
584
585 ChessSquare XiangqiArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
587         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
589         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
590 };
591
592 ChessSquare CapablancaArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
594         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
596         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
597 };
598
599 ChessSquare GreatArray[2][BOARD_FILES] = {
600     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
601         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
602     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
603         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
604 };
605
606 ChessSquare JanusArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
608         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
609     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
610         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
611 };
612
613 ChessSquare GrandArray[2][BOARD_FILES] = {
614     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
615         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
616     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
617         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
618 };
619
620 #ifdef GOTHIC
621 ChessSquare GothicArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
623         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
625         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
626 };
627 #else // !GOTHIC
628 #define GothicArray CapablancaArray
629 #endif // !GOTHIC
630
631 #ifdef FALCON
632 ChessSquare FalconArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
634         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
636         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
637 };
638 #else // !FALCON
639 #define FalconArray CapablancaArray
640 #endif // !FALCON
641
642 #else // !(BOARD_FILES>=10)
643 #define XiangqiPosition FIDEArray
644 #define CapablancaArray FIDEArray
645 #define GothicArray FIDEArray
646 #define GreatArray FIDEArray
647 #endif // !(BOARD_FILES>=10)
648
649 #if (BOARD_FILES>=12)
650 ChessSquare CourierArray[2][BOARD_FILES] = {
651     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
652         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
653     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
654         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
655 };
656 ChessSquare ChuArray[6][BOARD_FILES] = {
657     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
658       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
659     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
660       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
661     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
662       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
663     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
664       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
665     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
666       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
667     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
668       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
669 };
670 #else // !(BOARD_FILES>=12)
671 #define CourierArray CapablancaArray
672 #define ChuArray CapablancaArray
673 #endif // !(BOARD_FILES>=12)
674
675
676 Board initialPosition;
677
678
679 /* Convert str to a rating. Checks for special cases of "----",
680
681    "++++", etc. Also strips ()'s */
682 int
683 string_to_rating (char *str)
684 {
685   while(*str && !isdigit(*str)) ++str;
686   if (!*str)
687     return 0;   /* One of the special "no rating" cases */
688   else
689     return atoi(str);
690 }
691
692 void
693 ClearProgramStats ()
694 {
695     /* Init programStats */
696     programStats.movelist[0] = 0;
697     programStats.depth = 0;
698     programStats.nr_moves = 0;
699     programStats.moves_left = 0;
700     programStats.nodes = 0;
701     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
702     programStats.score = 0;
703     programStats.got_only_move = 0;
704     programStats.got_fail = 0;
705     programStats.line_is_book = 0;
706 }
707
708 void
709 CommonEngineInit ()
710 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
711     if (appData.firstPlaysBlack) {
712         first.twoMachinesColor = "black\n";
713         second.twoMachinesColor = "white\n";
714     } else {
715         first.twoMachinesColor = "white\n";
716         second.twoMachinesColor = "black\n";
717     }
718
719     first.other = &second;
720     second.other = &first;
721
722     { float norm = 1;
723         if(appData.timeOddsMode) {
724             norm = appData.timeOdds[0];
725             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
726         }
727         first.timeOdds  = appData.timeOdds[0]/norm;
728         second.timeOdds = appData.timeOdds[1]/norm;
729     }
730
731     if(programVersion) free(programVersion);
732     if (appData.noChessProgram) {
733         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
734         sprintf(programVersion, "%s", PACKAGE_STRING);
735     } else {
736       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
737       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
738       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
739     }
740 }
741
742 void
743 UnloadEngine (ChessProgramState *cps)
744 {
745         /* Kill off first chess program */
746         if (cps->isr != NULL)
747           RemoveInputSource(cps->isr);
748         cps->isr = NULL;
749
750         if (cps->pr != NoProc) {
751             ExitAnalyzeMode();
752             DoSleep( appData.delayBeforeQuit );
753             SendToProgram("quit\n", cps);
754             DoSleep( appData.delayAfterQuit );
755             DestroyChildProcess(cps->pr, cps->useSigterm);
756         }
757         cps->pr = NoProc;
758         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
759 }
760
761 void
762 ClearOptions (ChessProgramState *cps)
763 {
764     int i;
765     cps->nrOptions = cps->comboCnt = 0;
766     for(i=0; i<MAX_OPTIONS; i++) {
767         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
768         cps->option[i].textValue = 0;
769     }
770 }
771
772 char *engineNames[] = {
773   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
774      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
775 N_("first"),
776   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
777      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
778 N_("second")
779 };
780
781 void
782 InitEngine (ChessProgramState *cps, int n)
783 {   // [HGM] all engine initialiation put in a function that does one engine
784
785     ClearOptions(cps);
786
787     cps->which = engineNames[n];
788     cps->maybeThinking = FALSE;
789     cps->pr = NoProc;
790     cps->isr = NULL;
791     cps->sendTime = 2;
792     cps->sendDrawOffers = 1;
793
794     cps->program = appData.chessProgram[n];
795     cps->host = appData.host[n];
796     cps->dir = appData.directory[n];
797     cps->initString = appData.engInitString[n];
798     cps->computerString = appData.computerString[n];
799     cps->useSigint  = TRUE;
800     cps->useSigterm = TRUE;
801     cps->reuse = appData.reuse[n];
802     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
803     cps->useSetboard = FALSE;
804     cps->useSAN = FALSE;
805     cps->usePing = FALSE;
806     cps->lastPing = 0;
807     cps->lastPong = 0;
808     cps->usePlayother = FALSE;
809     cps->useColors = TRUE;
810     cps->useUsermove = FALSE;
811     cps->sendICS = FALSE;
812     cps->sendName = appData.icsActive;
813     cps->sdKludge = FALSE;
814     cps->stKludge = FALSE;
815     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
816     TidyProgramName(cps->program, cps->host, cps->tidy);
817     cps->matchWins = 0;
818     ASSIGN(cps->variants, appData.variant);
819     cps->analysisSupport = 2; /* detect */
820     cps->analyzing = FALSE;
821     cps->initDone = FALSE;
822     cps->reload = FALSE;
823
824     /* New features added by Tord: */
825     cps->useFEN960 = FALSE;
826     cps->useOOCastle = TRUE;
827     /* End of new features added by Tord. */
828     cps->fenOverride  = appData.fenOverride[n];
829
830     /* [HGM] time odds: set factor for each machine */
831     cps->timeOdds  = appData.timeOdds[n];
832
833     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
834     cps->accumulateTC = appData.accumulateTC[n];
835     cps->maxNrOfSessions = 1;
836
837     /* [HGM] debug */
838     cps->debug = FALSE;
839
840     cps->supportsNPS = UNKNOWN;
841     cps->memSize = FALSE;
842     cps->maxCores = FALSE;
843     ASSIGN(cps->egtFormats, "");
844
845     /* [HGM] options */
846     cps->optionSettings  = appData.engOptions[n];
847
848     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
849     cps->isUCI = appData.isUCI[n]; /* [AS] */
850     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
851     cps->highlight = 0;
852
853     if (appData.protocolVersion[n] > PROTOVER
854         || appData.protocolVersion[n] < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.protocolVersion[n]);
861         if( (len >= MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         cps->protocolVersion = appData.protocolVersion[n];
869       }
870
871     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
872     ParseFeatures(appData.featureDefaults, cps);
873 }
874
875 ChessProgramState *savCps;
876
877 GameMode oldMode;
878
879 void
880 LoadEngine ()
881 {
882     int i;
883     if(WaitForEngine(savCps, LoadEngine)) return;
884     CommonEngineInit(); // recalculate time odds
885     if(gameInfo.variant != StringToVariant(appData.variant)) {
886         // we changed variant when loading the engine; this forces us to reset
887         Reset(TRUE, savCps != &first);
888         oldMode = BeginningOfGame; // to prevent restoring old mode
889     }
890     InitChessProgram(savCps, FALSE);
891     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
892     DisplayMessage("", "");
893     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
894     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
895     ThawUI();
896     SetGNUMode();
897     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
898 }
899
900 void
901 ReplaceEngine (ChessProgramState *cps, int n)
902 {
903     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
904     keepInfo = 1;
905     if(oldMode != BeginningOfGame) EditGameEvent();
906     keepInfo = 0;
907     UnloadEngine(cps);
908     appData.noChessProgram = FALSE;
909     appData.clockMode = TRUE;
910     InitEngine(cps, n);
911     UpdateLogos(TRUE);
912     if(n) return; // only startup first engine immediately; second can wait
913     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
914     LoadEngine();
915 }
916
917 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
918 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
919
920 static char resetOptions[] =
921         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
922         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
923         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
924         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
925
926 void
927 FloatToFront(char **list, char *engineLine)
928 {
929     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
930     int i=0;
931     if(appData.recentEngines <= 0) return;
932     TidyProgramName(engineLine, "localhost", tidy+1);
933     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
934     strncpy(buf+1, *list, MSG_SIZ-50);
935     if(p = strstr(buf, tidy)) { // tidy name appears in list
936         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
937         while(*p++ = *++q); // squeeze out
938     }
939     strcat(tidy, buf+1); // put list behind tidy name
940     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
941     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
942     ASSIGN(*list, tidy+1);
943 }
944
945 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
946
947 void
948 Load (ChessProgramState *cps, int i)
949 {
950     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
951     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
952         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
953         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
954         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
955         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
956         appData.firstProtocolVersion = PROTOVER;
957         ParseArgsFromString(buf);
958         SwapEngines(i);
959         ReplaceEngine(cps, i);
960         FloatToFront(&appData.recentEngineList, engineLine);
961         return;
962     }
963     p = engineName;
964     while(q = strchr(p, SLASH)) p = q+1;
965     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
966     if(engineDir[0] != NULLCHAR) {
967         ASSIGN(appData.directory[i], engineDir); p = engineName;
968     } else if(p != engineName) { // derive directory from engine path, when not given
969         p[-1] = 0;
970         ASSIGN(appData.directory[i], engineName);
971         p[-1] = SLASH;
972         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
973     } else { ASSIGN(appData.directory[i], "."); }
974     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
975     if(params[0]) {
976         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
977         snprintf(command, MSG_SIZ, "%s %s", p, params);
978         p = command;
979     }
980     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
981     ASSIGN(appData.chessProgram[i], p);
982     appData.isUCI[i] = isUCI;
983     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
984     appData.hasOwnBookUCI[i] = hasBook;
985     if(!nickName[0]) useNick = FALSE;
986     if(useNick) ASSIGN(appData.pgnName[i], nickName);
987     if(addToList) {
988         int len;
989         char quote;
990         q = firstChessProgramNames;
991         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
992         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
993         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
994                         quote, p, quote, appData.directory[i],
995                         useNick ? " -fn \"" : "",
996                         useNick ? nickName : "",
997                         useNick ? "\"" : "",
998                         v1 ? " -firstProtocolVersion 1" : "",
999                         hasBook ? "" : " -fNoOwnBookUCI",
1000                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1001                         storeVariant ? " -variant " : "",
1002                         storeVariant ? VariantName(gameInfo.variant) : "");
1003         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1004         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1005         if(insert != q) insert[-1] = NULLCHAR;
1006         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1007         if(q)   free(q);
1008         FloatToFront(&appData.recentEngineList, buf);
1009     }
1010     ReplaceEngine(cps, i);
1011 }
1012
1013 void
1014 InitTimeControls ()
1015 {
1016     int matched, min, sec;
1017     /*
1018      * Parse timeControl resource
1019      */
1020     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1021                           appData.movesPerSession)) {
1022         char buf[MSG_SIZ];
1023         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1024         DisplayFatalError(buf, 0, 2);
1025     }
1026
1027     /*
1028      * Parse searchTime resource
1029      */
1030     if (*appData.searchTime != NULLCHAR) {
1031         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1032         if (matched == 1) {
1033             searchTime = min * 60;
1034         } else if (matched == 2) {
1035             searchTime = min * 60 + sec;
1036         } else {
1037             char buf[MSG_SIZ];
1038             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1039             DisplayFatalError(buf, 0, 2);
1040         }
1041     }
1042 }
1043
1044 void
1045 InitBackEnd1 ()
1046 {
1047
1048     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1049     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1050
1051     GetTimeMark(&programStartTime);
1052     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1053     appData.seedBase = random() + (random()<<15);
1054     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1055
1056     ClearProgramStats();
1057     programStats.ok_to_send = 1;
1058     programStats.seen_stat = 0;
1059
1060     /*
1061      * Initialize game list
1062      */
1063     ListNew(&gameList);
1064
1065
1066     /*
1067      * Internet chess server status
1068      */
1069     if (appData.icsActive) {
1070         appData.matchMode = FALSE;
1071         appData.matchGames = 0;
1072 #if ZIPPY
1073         appData.noChessProgram = !appData.zippyPlay;
1074 #else
1075         appData.zippyPlay = FALSE;
1076         appData.zippyTalk = FALSE;
1077         appData.noChessProgram = TRUE;
1078 #endif
1079         if (*appData.icsHelper != NULLCHAR) {
1080             appData.useTelnet = TRUE;
1081             appData.telnetProgram = appData.icsHelper;
1082         }
1083     } else {
1084         appData.zippyTalk = appData.zippyPlay = FALSE;
1085     }
1086
1087     /* [AS] Initialize pv info list [HGM] and game state */
1088     {
1089         int i, j;
1090
1091         for( i=0; i<=framePtr; i++ ) {
1092             pvInfoList[i].depth = -1;
1093             boards[i][EP_STATUS] = EP_NONE;
1094             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1095         }
1096     }
1097
1098     InitTimeControls();
1099
1100     /* [AS] Adjudication threshold */
1101     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1102
1103     InitEngine(&first, 0);
1104     InitEngine(&second, 1);
1105     CommonEngineInit();
1106
1107     pairing.which = "pairing"; // pairing engine
1108     pairing.pr = NoProc;
1109     pairing.isr = NULL;
1110     pairing.program = appData.pairingEngine;
1111     pairing.host = "localhost";
1112     pairing.dir = ".";
1113
1114     if (appData.icsActive) {
1115         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1116     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1117         appData.clockMode = FALSE;
1118         first.sendTime = second.sendTime = 0;
1119     }
1120
1121 #if ZIPPY
1122     /* Override some settings from environment variables, for backward
1123        compatibility.  Unfortunately it's not feasible to have the env
1124        vars just set defaults, at least in xboard.  Ugh.
1125     */
1126     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1127       ZippyInit();
1128     }
1129 #endif
1130
1131     if (!appData.icsActive) {
1132       char buf[MSG_SIZ];
1133       int len;
1134
1135       /* Check for variants that are supported only in ICS mode,
1136          or not at all.  Some that are accepted here nevertheless
1137          have bugs; see comments below.
1138       */
1139       VariantClass variant = StringToVariant(appData.variant);
1140       switch (variant) {
1141       case VariantBughouse:     /* need four players and two boards */
1142       case VariantKriegspiel:   /* need to hide pieces and move details */
1143         /* case VariantFischeRandom: (Fabien: moved below) */
1144         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1145         if( (len >= MSG_SIZ) && appData.debugMode )
1146           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1147
1148         DisplayFatalError(buf, 0, 2);
1149         return;
1150
1151       case VariantUnknown:
1152       case VariantLoadable:
1153       case Variant29:
1154       case Variant30:
1155       case Variant31:
1156       case Variant32:
1157       case Variant33:
1158       case Variant34:
1159       case Variant35:
1160       case Variant36:
1161       default:
1162         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1163         if( (len >= MSG_SIZ) && appData.debugMode )
1164           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1165
1166         DisplayFatalError(buf, 0, 2);
1167         return;
1168
1169       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1170       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1171       case VariantGothic:     /* [HGM] should work */
1172       case VariantCapablanca: /* [HGM] should work */
1173       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1174       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1175       case VariantChu:        /* [HGM] experimental */
1176       case VariantKnightmate: /* [HGM] should work */
1177       case VariantCylinder:   /* [HGM] untested */
1178       case VariantFalcon:     /* [HGM] untested */
1179       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1180                                  offboard interposition not understood */
1181       case VariantNormal:     /* definitely works! */
1182       case VariantWildCastle: /* pieces not automatically shuffled */
1183       case VariantNoCastle:   /* pieces not automatically shuffled */
1184       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1185       case VariantLosers:     /* should work except for win condition,
1186                                  and doesn't know captures are mandatory */
1187       case VariantSuicide:    /* should work except for win condition,
1188                                  and doesn't know captures are mandatory */
1189       case VariantGiveaway:   /* should work except for win condition,
1190                                  and doesn't know captures are mandatory */
1191       case VariantTwoKings:   /* should work */
1192       case VariantAtomic:     /* should work except for win condition */
1193       case Variant3Check:     /* should work except for win condition */
1194       case VariantShatranj:   /* should work except for all win conditions */
1195       case VariantMakruk:     /* should work except for draw countdown */
1196       case VariantASEAN :     /* should work except for draw countdown */
1197       case VariantBerolina:   /* might work if TestLegality is off */
1198       case VariantCapaRandom: /* should work */
1199       case VariantJanus:      /* should work */
1200       case VariantSuper:      /* experimental */
1201       case VariantGreat:      /* experimental, requires legality testing to be off */
1202       case VariantSChess:     /* S-Chess, should work */
1203       case VariantGrand:      /* should work */
1204       case VariantSpartan:    /* should work */
1205       case VariantLion:       /* should work */
1206         break;
1207       }
1208     }
1209
1210 }
1211
1212 int
1213 NextIntegerFromString (char ** str, long * value)
1214 {
1215     int result = -1;
1216     char * s = *str;
1217
1218     while( *s == ' ' || *s == '\t' ) {
1219         s++;
1220     }
1221
1222     *value = 0;
1223
1224     if( *s >= '0' && *s <= '9' ) {
1225         while( *s >= '0' && *s <= '9' ) {
1226             *value = *value * 10 + (*s - '0');
1227             s++;
1228         }
1229
1230         result = 0;
1231     }
1232
1233     *str = s;
1234
1235     return result;
1236 }
1237
1238 int
1239 NextTimeControlFromString (char ** str, long * value)
1240 {
1241     long temp;
1242     int result = NextIntegerFromString( str, &temp );
1243
1244     if( result == 0 ) {
1245         *value = temp * 60; /* Minutes */
1246         if( **str == ':' ) {
1247             (*str)++;
1248             result = NextIntegerFromString( str, &temp );
1249             *value += temp; /* Seconds */
1250         }
1251     }
1252
1253     return result;
1254 }
1255
1256 int
1257 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1258 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1259     int result = -1, type = 0; long temp, temp2;
1260
1261     if(**str != ':') return -1; // old params remain in force!
1262     (*str)++;
1263     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1264     if( NextIntegerFromString( str, &temp ) ) return -1;
1265     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1266
1267     if(**str != '/') {
1268         /* time only: incremental or sudden-death time control */
1269         if(**str == '+') { /* increment follows; read it */
1270             (*str)++;
1271             if(**str == '!') type = *(*str)++; // Bronstein TC
1272             if(result = NextIntegerFromString( str, &temp2)) return -1;
1273             *inc = temp2 * 1000;
1274             if(**str == '.') { // read fraction of increment
1275                 char *start = ++(*str);
1276                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1277                 temp2 *= 1000;
1278                 while(start++ < *str) temp2 /= 10;
1279                 *inc += temp2;
1280             }
1281         } else *inc = 0;
1282         *moves = 0; *tc = temp * 1000; *incType = type;
1283         return 0;
1284     }
1285
1286     (*str)++; /* classical time control */
1287     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1288
1289     if(result == 0) {
1290         *moves = temp;
1291         *tc    = temp2 * 1000;
1292         *inc   = 0;
1293         *incType = type;
1294     }
1295     return result;
1296 }
1297
1298 int
1299 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1300 {   /* [HGM] get time to add from the multi-session time-control string */
1301     int incType, moves=1; /* kludge to force reading of first session */
1302     long time, increment;
1303     char *s = tcString;
1304
1305     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1306     do {
1307         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1308         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1309         if(movenr == -1) return time;    /* last move before new session     */
1310         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1311         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1312         if(!moves) return increment;     /* current session is incremental   */
1313         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1314     } while(movenr >= -1);               /* try again for next session       */
1315
1316     return 0; // no new time quota on this move
1317 }
1318
1319 int
1320 ParseTimeControl (char *tc, float ti, int mps)
1321 {
1322   long tc1;
1323   long tc2;
1324   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1325   int min, sec=0;
1326
1327   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1328   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1329       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1330   if(ti > 0) {
1331
1332     if(mps)
1333       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1334     else
1335       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1336   } else {
1337     if(mps)
1338       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1339     else
1340       snprintf(buf, MSG_SIZ, ":%s", mytc);
1341   }
1342   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1343
1344   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1345     return FALSE;
1346   }
1347
1348   if( *tc == '/' ) {
1349     /* Parse second time control */
1350     tc++;
1351
1352     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1353       return FALSE;
1354     }
1355
1356     if( tc2 == 0 ) {
1357       return FALSE;
1358     }
1359
1360     timeControl_2 = tc2 * 1000;
1361   }
1362   else {
1363     timeControl_2 = 0;
1364   }
1365
1366   if( tc1 == 0 ) {
1367     return FALSE;
1368   }
1369
1370   timeControl = tc1 * 1000;
1371
1372   if (ti >= 0) {
1373     timeIncrement = ti * 1000;  /* convert to ms */
1374     movesPerSession = 0;
1375   } else {
1376     timeIncrement = 0;
1377     movesPerSession = mps;
1378   }
1379   return TRUE;
1380 }
1381
1382 void
1383 InitBackEnd2 ()
1384 {
1385     if (appData.debugMode) {
1386 #    ifdef __GIT_VERSION
1387       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1388 #    else
1389       fprintf(debugFP, "Version: %s\n", programVersion);
1390 #    endif
1391     }
1392     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1393
1394     set_cont_sequence(appData.wrapContSeq);
1395     if (appData.matchGames > 0) {
1396         appData.matchMode = TRUE;
1397     } else if (appData.matchMode) {
1398         appData.matchGames = 1;
1399     }
1400     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1401         appData.matchGames = appData.sameColorGames;
1402     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1403         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1404         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1405     }
1406     Reset(TRUE, FALSE);
1407     if (appData.noChessProgram || first.protocolVersion == 1) {
1408       InitBackEnd3();
1409     } else {
1410       /* kludge: allow timeout for initial "feature" commands */
1411       FreezeUI();
1412       DisplayMessage("", _("Starting chess program"));
1413       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1414     }
1415 }
1416
1417 int
1418 CalculateIndex (int index, int gameNr)
1419 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1420     int res;
1421     if(index > 0) return index; // fixed nmber
1422     if(index == 0) return 1;
1423     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1424     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1425     return res;
1426 }
1427
1428 int
1429 LoadGameOrPosition (int gameNr)
1430 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1431     if (*appData.loadGameFile != NULLCHAR) {
1432         if (!LoadGameFromFile(appData.loadGameFile,
1433                 CalculateIndex(appData.loadGameIndex, gameNr),
1434                               appData.loadGameFile, FALSE)) {
1435             DisplayFatalError(_("Bad game file"), 0, 1);
1436             return 0;
1437         }
1438     } else if (*appData.loadPositionFile != NULLCHAR) {
1439         if (!LoadPositionFromFile(appData.loadPositionFile,
1440                 CalculateIndex(appData.loadPositionIndex, gameNr),
1441                                   appData.loadPositionFile)) {
1442             DisplayFatalError(_("Bad position file"), 0, 1);
1443             return 0;
1444         }
1445     }
1446     return 1;
1447 }
1448
1449 void
1450 ReserveGame (int gameNr, char resChar)
1451 {
1452     FILE *tf = fopen(appData.tourneyFile, "r+");
1453     char *p, *q, c, buf[MSG_SIZ];
1454     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1455     safeStrCpy(buf, lastMsg, MSG_SIZ);
1456     DisplayMessage(_("Pick new game"), "");
1457     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1458     ParseArgsFromFile(tf);
1459     p = q = appData.results;
1460     if(appData.debugMode) {
1461       char *r = appData.participants;
1462       fprintf(debugFP, "results = '%s'\n", p);
1463       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1464       fprintf(debugFP, "\n");
1465     }
1466     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1467     nextGame = q - p;
1468     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1469     safeStrCpy(q, p, strlen(p) + 2);
1470     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1471     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1472     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1473         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1474         q[nextGame] = '*';
1475     }
1476     fseek(tf, -(strlen(p)+4), SEEK_END);
1477     c = fgetc(tf);
1478     if(c != '"') // depending on DOS or Unix line endings we can be one off
1479          fseek(tf, -(strlen(p)+2), SEEK_END);
1480     else fseek(tf, -(strlen(p)+3), SEEK_END);
1481     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1482     DisplayMessage(buf, "");
1483     free(p); appData.results = q;
1484     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1485        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1486       int round = appData.defaultMatchGames * appData.tourneyType;
1487       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1488          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1489         UnloadEngine(&first);  // next game belongs to other pairing;
1490         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1491     }
1492     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1493 }
1494
1495 void
1496 MatchEvent (int mode)
1497 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1498         int dummy;
1499         if(matchMode) { // already in match mode: switch it off
1500             abortMatch = TRUE;
1501             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1502             return;
1503         }
1504 //      if(gameMode != BeginningOfGame) {
1505 //          DisplayError(_("You can only start a match from the initial position."), 0);
1506 //          return;
1507 //      }
1508         abortMatch = FALSE;
1509         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1510         /* Set up machine vs. machine match */
1511         nextGame = 0;
1512         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1513         if(appData.tourneyFile[0]) {
1514             ReserveGame(-1, 0);
1515             if(nextGame > appData.matchGames) {
1516                 char buf[MSG_SIZ];
1517                 if(strchr(appData.results, '*') == NULL) {
1518                     FILE *f;
1519                     appData.tourneyCycles++;
1520                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1521                         fclose(f);
1522                         NextTourneyGame(-1, &dummy);
1523                         ReserveGame(-1, 0);
1524                         if(nextGame <= appData.matchGames) {
1525                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1526                             matchMode = mode;
1527                             ScheduleDelayedEvent(NextMatchGame, 10000);
1528                             return;
1529                         }
1530                     }
1531                 }
1532                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1533                 DisplayError(buf, 0);
1534                 appData.tourneyFile[0] = 0;
1535                 return;
1536             }
1537         } else
1538         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1539             DisplayFatalError(_("Can't have a match with no chess programs"),
1540                               0, 2);
1541             return;
1542         }
1543         matchMode = mode;
1544         matchGame = roundNr = 1;
1545         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1546         NextMatchGame();
1547 }
1548
1549 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1550
1551 void
1552 InitBackEnd3 P((void))
1553 {
1554     GameMode initialMode;
1555     char buf[MSG_SIZ];
1556     int err, len;
1557
1558     InitChessProgram(&first, startedFromSetupPosition);
1559
1560     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1561         free(programVersion);
1562         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1563         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1564         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1565     }
1566
1567     if (appData.icsActive) {
1568 #ifdef WIN32
1569         /* [DM] Make a console window if needed [HGM] merged ifs */
1570         ConsoleCreate();
1571 #endif
1572         err = establish();
1573         if (err != 0)
1574           {
1575             if (*appData.icsCommPort != NULLCHAR)
1576               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1577                              appData.icsCommPort);
1578             else
1579               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1580                         appData.icsHost, appData.icsPort);
1581
1582             if( (len >= MSG_SIZ) && appData.debugMode )
1583               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584
1585             DisplayFatalError(buf, err, 1);
1586             return;
1587         }
1588         SetICSMode();
1589         telnetISR =
1590           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1591         fromUserISR =
1592           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1593         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1594             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1595     } else if (appData.noChessProgram) {
1596         SetNCPMode();
1597     } else {
1598         SetGNUMode();
1599     }
1600
1601     if (*appData.cmailGameName != NULLCHAR) {
1602         SetCmailMode();
1603         OpenLoopback(&cmailPR);
1604         cmailISR =
1605           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1606     }
1607
1608     ThawUI();
1609     DisplayMessage("", "");
1610     if (StrCaseCmp(appData.initialMode, "") == 0) {
1611       initialMode = BeginningOfGame;
1612       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1613         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1614         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1615         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1616         ModeHighlight();
1617       }
1618     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1619       initialMode = TwoMachinesPlay;
1620     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1621       initialMode = AnalyzeFile;
1622     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1623       initialMode = AnalyzeMode;
1624     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1625       initialMode = MachinePlaysWhite;
1626     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1627       initialMode = MachinePlaysBlack;
1628     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1629       initialMode = EditGame;
1630     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1631       initialMode = EditPosition;
1632     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1633       initialMode = Training;
1634     } else {
1635       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1636       if( (len >= MSG_SIZ) && appData.debugMode )
1637         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1638
1639       DisplayFatalError(buf, 0, 2);
1640       return;
1641     }
1642
1643     if (appData.matchMode) {
1644         if(appData.tourneyFile[0]) { // start tourney from command line
1645             FILE *f;
1646             if(f = fopen(appData.tourneyFile, "r")) {
1647                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1648                 fclose(f);
1649                 appData.clockMode = TRUE;
1650                 SetGNUMode();
1651             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1652         }
1653         MatchEvent(TRUE);
1654     } else if (*appData.cmailGameName != NULLCHAR) {
1655         /* Set up cmail mode */
1656         ReloadCmailMsgEvent(TRUE);
1657     } else {
1658         /* Set up other modes */
1659         if (initialMode == AnalyzeFile) {
1660           if (*appData.loadGameFile == NULLCHAR) {
1661             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1662             return;
1663           }
1664         }
1665         if (*appData.loadGameFile != NULLCHAR) {
1666             (void) LoadGameFromFile(appData.loadGameFile,
1667                                     appData.loadGameIndex,
1668                                     appData.loadGameFile, TRUE);
1669         } else if (*appData.loadPositionFile != NULLCHAR) {
1670             (void) LoadPositionFromFile(appData.loadPositionFile,
1671                                         appData.loadPositionIndex,
1672                                         appData.loadPositionFile);
1673             /* [HGM] try to make self-starting even after FEN load */
1674             /* to allow automatic setup of fairy variants with wtm */
1675             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1676                 gameMode = BeginningOfGame;
1677                 setboardSpoiledMachineBlack = 1;
1678             }
1679             /* [HGM] loadPos: make that every new game uses the setup */
1680             /* from file as long as we do not switch variant          */
1681             if(!blackPlaysFirst) {
1682                 startedFromPositionFile = TRUE;
1683                 CopyBoard(filePosition, boards[0]);
1684             }
1685         }
1686         if (initialMode == AnalyzeMode) {
1687           if (appData.noChessProgram) {
1688             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1689             return;
1690           }
1691           if (appData.icsActive) {
1692             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1693             return;
1694           }
1695           AnalyzeModeEvent();
1696         } else if (initialMode == AnalyzeFile) {
1697           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1698           ShowThinkingEvent();
1699           AnalyzeFileEvent();
1700           AnalysisPeriodicEvent(1);
1701         } else if (initialMode == MachinePlaysWhite) {
1702           if (appData.noChessProgram) {
1703             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1704                               0, 2);
1705             return;
1706           }
1707           if (appData.icsActive) {
1708             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1709                               0, 2);
1710             return;
1711           }
1712           MachineWhiteEvent();
1713         } else if (initialMode == MachinePlaysBlack) {
1714           if (appData.noChessProgram) {
1715             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1716                               0, 2);
1717             return;
1718           }
1719           if (appData.icsActive) {
1720             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1721                               0, 2);
1722             return;
1723           }
1724           MachineBlackEvent();
1725         } else if (initialMode == TwoMachinesPlay) {
1726           if (appData.noChessProgram) {
1727             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1728                               0, 2);
1729             return;
1730           }
1731           if (appData.icsActive) {
1732             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1733                               0, 2);
1734             return;
1735           }
1736           TwoMachinesEvent();
1737         } else if (initialMode == EditGame) {
1738           EditGameEvent();
1739         } else if (initialMode == EditPosition) {
1740           EditPositionEvent();
1741         } else if (initialMode == Training) {
1742           if (*appData.loadGameFile == NULLCHAR) {
1743             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1744             return;
1745           }
1746           TrainingEvent();
1747         }
1748     }
1749 }
1750
1751 void
1752 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1753 {
1754     DisplayBook(current+1);
1755
1756     MoveHistorySet( movelist, first, last, current, pvInfoList );
1757
1758     EvalGraphSet( first, last, current, pvInfoList );
1759
1760     MakeEngineOutputTitle();
1761 }
1762
1763 /*
1764  * Establish will establish a contact to a remote host.port.
1765  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1766  *  used to talk to the host.
1767  * Returns 0 if okay, error code if not.
1768  */
1769 int
1770 establish ()
1771 {
1772     char buf[MSG_SIZ];
1773
1774     if (*appData.icsCommPort != NULLCHAR) {
1775         /* Talk to the host through a serial comm port */
1776         return OpenCommPort(appData.icsCommPort, &icsPR);
1777
1778     } else if (*appData.gateway != NULLCHAR) {
1779         if (*appData.remoteShell == NULLCHAR) {
1780             /* Use the rcmd protocol to run telnet program on a gateway host */
1781             snprintf(buf, sizeof(buf), "%s %s %s",
1782                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1783             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1784
1785         } else {
1786             /* Use the rsh program to run telnet program on a gateway host */
1787             if (*appData.remoteUser == NULLCHAR) {
1788                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1789                         appData.gateway, appData.telnetProgram,
1790                         appData.icsHost, appData.icsPort);
1791             } else {
1792                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1793                         appData.remoteShell, appData.gateway,
1794                         appData.remoteUser, appData.telnetProgram,
1795                         appData.icsHost, appData.icsPort);
1796             }
1797             return StartChildProcess(buf, "", &icsPR);
1798
1799         }
1800     } else if (appData.useTelnet) {
1801         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1802
1803     } else {
1804         /* TCP socket interface differs somewhat between
1805            Unix and NT; handle details in the front end.
1806            */
1807         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1808     }
1809 }
1810
1811 void
1812 EscapeExpand (char *p, char *q)
1813 {       // [HGM] initstring: routine to shape up string arguments
1814         while(*p++ = *q++) if(p[-1] == '\\')
1815             switch(*q++) {
1816                 case 'n': p[-1] = '\n'; break;
1817                 case 'r': p[-1] = '\r'; break;
1818                 case 't': p[-1] = '\t'; break;
1819                 case '\\': p[-1] = '\\'; break;
1820                 case 0: *p = 0; return;
1821                 default: p[-1] = q[-1]; break;
1822             }
1823 }
1824
1825 void
1826 show_bytes (FILE *fp, char *buf, int count)
1827 {
1828     while (count--) {
1829         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1830             fprintf(fp, "\\%03o", *buf & 0xff);
1831         } else {
1832             putc(*buf, fp);
1833         }
1834         buf++;
1835     }
1836     fflush(fp);
1837 }
1838
1839 /* Returns an errno value */
1840 int
1841 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1842 {
1843     char buf[8192], *p, *q, *buflim;
1844     int left, newcount, outcount;
1845
1846     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1847         *appData.gateway != NULLCHAR) {
1848         if (appData.debugMode) {
1849             fprintf(debugFP, ">ICS: ");
1850             show_bytes(debugFP, message, count);
1851             fprintf(debugFP, "\n");
1852         }
1853         return OutputToProcess(pr, message, count, outError);
1854     }
1855
1856     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1857     p = message;
1858     q = buf;
1859     left = count;
1860     newcount = 0;
1861     while (left) {
1862         if (q >= buflim) {
1863             if (appData.debugMode) {
1864                 fprintf(debugFP, ">ICS: ");
1865                 show_bytes(debugFP, buf, newcount);
1866                 fprintf(debugFP, "\n");
1867             }
1868             outcount = OutputToProcess(pr, buf, newcount, outError);
1869             if (outcount < newcount) return -1; /* to be sure */
1870             q = buf;
1871             newcount = 0;
1872         }
1873         if (*p == '\n') {
1874             *q++ = '\r';
1875             newcount++;
1876         } else if (((unsigned char) *p) == TN_IAC) {
1877             *q++ = (char) TN_IAC;
1878             newcount ++;
1879         }
1880         *q++ = *p++;
1881         newcount++;
1882         left--;
1883     }
1884     if (appData.debugMode) {
1885         fprintf(debugFP, ">ICS: ");
1886         show_bytes(debugFP, buf, newcount);
1887         fprintf(debugFP, "\n");
1888     }
1889     outcount = OutputToProcess(pr, buf, newcount, outError);
1890     if (outcount < newcount) return -1; /* to be sure */
1891     return count;
1892 }
1893
1894 void
1895 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1896 {
1897     int outError, outCount;
1898     static int gotEof = 0;
1899     static FILE *ini;
1900
1901     /* Pass data read from player on to ICS */
1902     if (count > 0) {
1903         gotEof = 0;
1904         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1905         if (outCount < count) {
1906             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1907         }
1908         if(have_sent_ICS_logon == 2) {
1909           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1910             fprintf(ini, "%s", message);
1911             have_sent_ICS_logon = 3;
1912           } else
1913             have_sent_ICS_logon = 1;
1914         } else if(have_sent_ICS_logon == 3) {
1915             fprintf(ini, "%s", message);
1916             fclose(ini);
1917           have_sent_ICS_logon = 1;
1918         }
1919     } else if (count < 0) {
1920         RemoveInputSource(isr);
1921         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1922     } else if (gotEof++ > 0) {
1923         RemoveInputSource(isr);
1924         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1925     }
1926 }
1927
1928 void
1929 KeepAlive ()
1930 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1931     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1932     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1933     SendToICS("date\n");
1934     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1935 }
1936
1937 /* added routine for printf style output to ics */
1938 void
1939 ics_printf (char *format, ...)
1940 {
1941     char buffer[MSG_SIZ];
1942     va_list args;
1943
1944     va_start(args, format);
1945     vsnprintf(buffer, sizeof(buffer), format, args);
1946     buffer[sizeof(buffer)-1] = '\0';
1947     SendToICS(buffer);
1948     va_end(args);
1949 }
1950
1951 void
1952 SendToICS (char *s)
1953 {
1954     int count, outCount, outError;
1955
1956     if (icsPR == NoProc) return;
1957
1958     count = strlen(s);
1959     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1960     if (outCount < count) {
1961         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1962     }
1963 }
1964
1965 /* This is used for sending logon scripts to the ICS. Sending
1966    without a delay causes problems when using timestamp on ICC
1967    (at least on my machine). */
1968 void
1969 SendToICSDelayed (char *s, long msdelay)
1970 {
1971     int count, outCount, outError;
1972
1973     if (icsPR == NoProc) return;
1974
1975     count = strlen(s);
1976     if (appData.debugMode) {
1977         fprintf(debugFP, ">ICS: ");
1978         show_bytes(debugFP, s, count);
1979         fprintf(debugFP, "\n");
1980     }
1981     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1982                                       msdelay);
1983     if (outCount < count) {
1984         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1985     }
1986 }
1987
1988
1989 /* Remove all highlighting escape sequences in s
1990    Also deletes any suffix starting with '('
1991    */
1992 char *
1993 StripHighlightAndTitle (char *s)
1994 {
1995     static char retbuf[MSG_SIZ];
1996     char *p = retbuf;
1997
1998     while (*s != NULLCHAR) {
1999         while (*s == '\033') {
2000             while (*s != NULLCHAR && !isalpha(*s)) s++;
2001             if (*s != NULLCHAR) s++;
2002         }
2003         while (*s != NULLCHAR && *s != '\033') {
2004             if (*s == '(' || *s == '[') {
2005                 *p = NULLCHAR;
2006                 return retbuf;
2007             }
2008             *p++ = *s++;
2009         }
2010     }
2011     *p = NULLCHAR;
2012     return retbuf;
2013 }
2014
2015 /* Remove all highlighting escape sequences in s */
2016 char *
2017 StripHighlight (char *s)
2018 {
2019     static char retbuf[MSG_SIZ];
2020     char *p = retbuf;
2021
2022     while (*s != NULLCHAR) {
2023         while (*s == '\033') {
2024             while (*s != NULLCHAR && !isalpha(*s)) s++;
2025             if (*s != NULLCHAR) s++;
2026         }
2027         while (*s != NULLCHAR && *s != '\033') {
2028             *p++ = *s++;
2029         }
2030     }
2031     *p = NULLCHAR;
2032     return retbuf;
2033 }
2034
2035 char engineVariant[MSG_SIZ];
2036 char *variantNames[] = VARIANT_NAMES;
2037 char *
2038 VariantName (VariantClass v)
2039 {
2040     if(v == VariantUnknown || *engineVariant) return engineVariant;
2041     return variantNames[v];
2042 }
2043
2044
2045 /* Identify a variant from the strings the chess servers use or the
2046    PGN Variant tag names we use. */
2047 VariantClass
2048 StringToVariant (char *e)
2049 {
2050     char *p;
2051     int wnum = -1;
2052     VariantClass v = VariantNormal;
2053     int i, found = FALSE;
2054     char buf[MSG_SIZ];
2055     int len;
2056
2057     if (!e) return v;
2058
2059     /* [HGM] skip over optional board-size prefixes */
2060     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2061         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2062         while( *e++ != '_');
2063     }
2064
2065     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2066         v = VariantNormal;
2067         found = TRUE;
2068     } else
2069     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2070       if (StrCaseStr(e, variantNames[i])) {
2071         v = (VariantClass) i;
2072         found = TRUE;
2073         break;
2074       }
2075     }
2076
2077     if (!found) {
2078       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2079           || StrCaseStr(e, "wild/fr")
2080           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2081         v = VariantFischeRandom;
2082       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2083                  (i = 1, p = StrCaseStr(e, "w"))) {
2084         p += i;
2085         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2086         if (isdigit(*p)) {
2087           wnum = atoi(p);
2088         } else {
2089           wnum = -1;
2090         }
2091         switch (wnum) {
2092         case 0: /* FICS only, actually */
2093         case 1:
2094           /* Castling legal even if K starts on d-file */
2095           v = VariantWildCastle;
2096           break;
2097         case 2:
2098         case 3:
2099         case 4:
2100           /* Castling illegal even if K & R happen to start in
2101              normal positions. */
2102           v = VariantNoCastle;
2103           break;
2104         case 5:
2105         case 7:
2106         case 8:
2107         case 10:
2108         case 11:
2109         case 12:
2110         case 13:
2111         case 14:
2112         case 15:
2113         case 18:
2114         case 19:
2115           /* Castling legal iff K & R start in normal positions */
2116           v = VariantNormal;
2117           break;
2118         case 6:
2119         case 20:
2120         case 21:
2121           /* Special wilds for position setup; unclear what to do here */
2122           v = VariantLoadable;
2123           break;
2124         case 9:
2125           /* Bizarre ICC game */
2126           v = VariantTwoKings;
2127           break;
2128         case 16:
2129           v = VariantKriegspiel;
2130           break;
2131         case 17:
2132           v = VariantLosers;
2133           break;
2134         case 22:
2135           v = VariantFischeRandom;
2136           break;
2137         case 23:
2138           v = VariantCrazyhouse;
2139           break;
2140         case 24:
2141           v = VariantBughouse;
2142           break;
2143         case 25:
2144           v = Variant3Check;
2145           break;
2146         case 26:
2147           /* Not quite the same as FICS suicide! */
2148           v = VariantGiveaway;
2149           break;
2150         case 27:
2151           v = VariantAtomic;
2152           break;
2153         case 28:
2154           v = VariantShatranj;
2155           break;
2156
2157         /* Temporary names for future ICC types.  The name *will* change in
2158            the next xboard/WinBoard release after ICC defines it. */
2159         case 29:
2160           v = Variant29;
2161           break;
2162         case 30:
2163           v = Variant30;
2164           break;
2165         case 31:
2166           v = Variant31;
2167           break;
2168         case 32:
2169           v = Variant32;
2170           break;
2171         case 33:
2172           v = Variant33;
2173           break;
2174         case 34:
2175           v = Variant34;
2176           break;
2177         case 35:
2178           v = Variant35;
2179           break;
2180         case 36:
2181           v = Variant36;
2182           break;
2183         case 37:
2184           v = VariantShogi;
2185           break;
2186         case 38:
2187           v = VariantXiangqi;
2188           break;
2189         case 39:
2190           v = VariantCourier;
2191           break;
2192         case 40:
2193           v = VariantGothic;
2194           break;
2195         case 41:
2196           v = VariantCapablanca;
2197           break;
2198         case 42:
2199           v = VariantKnightmate;
2200           break;
2201         case 43:
2202           v = VariantFairy;
2203           break;
2204         case 44:
2205           v = VariantCylinder;
2206           break;
2207         case 45:
2208           v = VariantFalcon;
2209           break;
2210         case 46:
2211           v = VariantCapaRandom;
2212           break;
2213         case 47:
2214           v = VariantBerolina;
2215           break;
2216         case 48:
2217           v = VariantJanus;
2218           break;
2219         case 49:
2220           v = VariantSuper;
2221           break;
2222         case 50:
2223           v = VariantGreat;
2224           break;
2225         case -1:
2226           /* Found "wild" or "w" in the string but no number;
2227              must assume it's normal chess. */
2228           v = VariantNormal;
2229           break;
2230         default:
2231           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2232           if( (len >= MSG_SIZ) && appData.debugMode )
2233             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2234
2235           DisplayError(buf, 0);
2236           v = VariantUnknown;
2237           break;
2238         }
2239       }
2240     }
2241     if (appData.debugMode) {
2242       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2243               e, wnum, VariantName(v));
2244     }
2245     return v;
2246 }
2247
2248 static int leftover_start = 0, leftover_len = 0;
2249 char star_match[STAR_MATCH_N][MSG_SIZ];
2250
2251 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2252    advance *index beyond it, and set leftover_start to the new value of
2253    *index; else return FALSE.  If pattern contains the character '*', it
2254    matches any sequence of characters not containing '\r', '\n', or the
2255    character following the '*' (if any), and the matched sequence(s) are
2256    copied into star_match.
2257    */
2258 int
2259 looking_at ( char *buf, int *index, char *pattern)
2260 {
2261     char *bufp = &buf[*index], *patternp = pattern;
2262     int star_count = 0;
2263     char *matchp = star_match[0];
2264
2265     for (;;) {
2266         if (*patternp == NULLCHAR) {
2267             *index = leftover_start = bufp - buf;
2268             *matchp = NULLCHAR;
2269             return TRUE;
2270         }
2271         if (*bufp == NULLCHAR) return FALSE;
2272         if (*patternp == '*') {
2273             if (*bufp == *(patternp + 1)) {
2274                 *matchp = NULLCHAR;
2275                 matchp = star_match[++star_count];
2276                 patternp += 2;
2277                 bufp++;
2278                 continue;
2279             } else if (*bufp == '\n' || *bufp == '\r') {
2280                 patternp++;
2281                 if (*patternp == NULLCHAR)
2282                   continue;
2283                 else
2284                   return FALSE;
2285             } else {
2286                 *matchp++ = *bufp++;
2287                 continue;
2288             }
2289         }
2290         if (*patternp != *bufp) return FALSE;
2291         patternp++;
2292         bufp++;
2293     }
2294 }
2295
2296 void
2297 SendToPlayer (char *data, int length)
2298 {
2299     int error, outCount;
2300     outCount = OutputToProcess(NoProc, data, length, &error);
2301     if (outCount < length) {
2302         DisplayFatalError(_("Error writing to display"), error, 1);
2303     }
2304 }
2305
2306 void
2307 PackHolding (char packed[], char *holding)
2308 {
2309     char *p = holding;
2310     char *q = packed;
2311     int runlength = 0;
2312     int curr = 9999;
2313     do {
2314         if (*p == curr) {
2315             runlength++;
2316         } else {
2317             switch (runlength) {
2318               case 0:
2319                 break;
2320               case 1:
2321                 *q++ = curr;
2322                 break;
2323               case 2:
2324                 *q++ = curr;
2325                 *q++ = curr;
2326                 break;
2327               default:
2328                 sprintf(q, "%d", runlength);
2329                 while (*q) q++;
2330                 *q++ = curr;
2331                 break;
2332             }
2333             runlength = 1;
2334             curr = *p;
2335         }
2336     } while (*p++);
2337     *q = NULLCHAR;
2338 }
2339
2340 /* Telnet protocol requests from the front end */
2341 void
2342 TelnetRequest (unsigned char ddww, unsigned char option)
2343 {
2344     unsigned char msg[3];
2345     int outCount, outError;
2346
2347     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2348
2349     if (appData.debugMode) {
2350         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2351         switch (ddww) {
2352           case TN_DO:
2353             ddwwStr = "DO";
2354             break;
2355           case TN_DONT:
2356             ddwwStr = "DONT";
2357             break;
2358           case TN_WILL:
2359             ddwwStr = "WILL";
2360             break;
2361           case TN_WONT:
2362             ddwwStr = "WONT";
2363             break;
2364           default:
2365             ddwwStr = buf1;
2366             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2367             break;
2368         }
2369         switch (option) {
2370           case TN_ECHO:
2371             optionStr = "ECHO";
2372             break;
2373           default:
2374             optionStr = buf2;
2375             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2376             break;
2377         }
2378         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2379     }
2380     msg[0] = TN_IAC;
2381     msg[1] = ddww;
2382     msg[2] = option;
2383     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2384     if (outCount < 3) {
2385         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2386     }
2387 }
2388
2389 void
2390 DoEcho ()
2391 {
2392     if (!appData.icsActive) return;
2393     TelnetRequest(TN_DO, TN_ECHO);
2394 }
2395
2396 void
2397 DontEcho ()
2398 {
2399     if (!appData.icsActive) return;
2400     TelnetRequest(TN_DONT, TN_ECHO);
2401 }
2402
2403 void
2404 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2405 {
2406     /* put the holdings sent to us by the server on the board holdings area */
2407     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2408     char p;
2409     ChessSquare piece;
2410
2411     if(gameInfo.holdingsWidth < 2)  return;
2412     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2413         return; // prevent overwriting by pre-board holdings
2414
2415     if( (int)lowestPiece >= BlackPawn ) {
2416         holdingsColumn = 0;
2417         countsColumn = 1;
2418         holdingsStartRow = BOARD_HEIGHT-1;
2419         direction = -1;
2420     } else {
2421         holdingsColumn = BOARD_WIDTH-1;
2422         countsColumn = BOARD_WIDTH-2;
2423         holdingsStartRow = 0;
2424         direction = 1;
2425     }
2426
2427     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2428         board[i][holdingsColumn] = EmptySquare;
2429         board[i][countsColumn]   = (ChessSquare) 0;
2430     }
2431     while( (p=*holdings++) != NULLCHAR ) {
2432         piece = CharToPiece( ToUpper(p) );
2433         if(piece == EmptySquare) continue;
2434         /*j = (int) piece - (int) WhitePawn;*/
2435         j = PieceToNumber(piece);
2436         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2437         if(j < 0) continue;               /* should not happen */
2438         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2439         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2440         board[holdingsStartRow+j*direction][countsColumn]++;
2441     }
2442 }
2443
2444
2445 void
2446 VariantSwitch (Board board, VariantClass newVariant)
2447 {
2448    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2449    static Board oldBoard;
2450
2451    startedFromPositionFile = FALSE;
2452    if(gameInfo.variant == newVariant) return;
2453
2454    /* [HGM] This routine is called each time an assignment is made to
2455     * gameInfo.variant during a game, to make sure the board sizes
2456     * are set to match the new variant. If that means adding or deleting
2457     * holdings, we shift the playing board accordingly
2458     * This kludge is needed because in ICS observe mode, we get boards
2459     * of an ongoing game without knowing the variant, and learn about the
2460     * latter only later. This can be because of the move list we requested,
2461     * in which case the game history is refilled from the beginning anyway,
2462     * but also when receiving holdings of a crazyhouse game. In the latter
2463     * case we want to add those holdings to the already received position.
2464     */
2465
2466
2467    if (appData.debugMode) {
2468      fprintf(debugFP, "Switch board from %s to %s\n",
2469              VariantName(gameInfo.variant), VariantName(newVariant));
2470      setbuf(debugFP, NULL);
2471    }
2472    shuffleOpenings = 0;       /* [HGM] shuffle */
2473    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2474    switch(newVariant)
2475      {
2476      case VariantShogi:
2477        newWidth = 9;  newHeight = 9;
2478        gameInfo.holdingsSize = 7;
2479      case VariantBughouse:
2480      case VariantCrazyhouse:
2481        newHoldingsWidth = 2; break;
2482      case VariantGreat:
2483        newWidth = 10;
2484      case VariantSuper:
2485        newHoldingsWidth = 2;
2486        gameInfo.holdingsSize = 8;
2487        break;
2488      case VariantGothic:
2489      case VariantCapablanca:
2490      case VariantCapaRandom:
2491        newWidth = 10;
2492      default:
2493        newHoldingsWidth = gameInfo.holdingsSize = 0;
2494      };
2495
2496    if(newWidth  != gameInfo.boardWidth  ||
2497       newHeight != gameInfo.boardHeight ||
2498       newHoldingsWidth != gameInfo.holdingsWidth ) {
2499
2500      /* shift position to new playing area, if needed */
2501      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2502        for(i=0; i<BOARD_HEIGHT; i++)
2503          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2504            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2505              board[i][j];
2506        for(i=0; i<newHeight; i++) {
2507          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2508          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2509        }
2510      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2511        for(i=0; i<BOARD_HEIGHT; i++)
2512          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2513            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2514              board[i][j];
2515      }
2516      board[HOLDINGS_SET] = 0;
2517      gameInfo.boardWidth  = newWidth;
2518      gameInfo.boardHeight = newHeight;
2519      gameInfo.holdingsWidth = newHoldingsWidth;
2520      gameInfo.variant = newVariant;
2521      InitDrawingSizes(-2, 0);
2522    } else gameInfo.variant = newVariant;
2523    CopyBoard(oldBoard, board);   // remember correctly formatted board
2524      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2525    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2526 }
2527
2528 static int loggedOn = FALSE;
2529
2530 /*-- Game start info cache: --*/
2531 int gs_gamenum;
2532 char gs_kind[MSG_SIZ];
2533 static char player1Name[128] = "";
2534 static char player2Name[128] = "";
2535 static char cont_seq[] = "\n\\   ";
2536 static int player1Rating = -1;
2537 static int player2Rating = -1;
2538 /*----------------------------*/
2539
2540 ColorClass curColor = ColorNormal;
2541 int suppressKibitz = 0;
2542
2543 // [HGM] seekgraph
2544 Boolean soughtPending = FALSE;
2545 Boolean seekGraphUp;
2546 #define MAX_SEEK_ADS 200
2547 #define SQUARE 0x80
2548 char *seekAdList[MAX_SEEK_ADS];
2549 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2550 float tcList[MAX_SEEK_ADS];
2551 char colorList[MAX_SEEK_ADS];
2552 int nrOfSeekAds = 0;
2553 int minRating = 1010, maxRating = 2800;
2554 int hMargin = 10, vMargin = 20, h, w;
2555 extern int squareSize, lineGap;
2556
2557 void
2558 PlotSeekAd (int i)
2559 {
2560         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2561         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2562         if(r < minRating+100 && r >=0 ) r = minRating+100;
2563         if(r > maxRating) r = maxRating;
2564         if(tc < 1.f) tc = 1.f;
2565         if(tc > 95.f) tc = 95.f;
2566         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2567         y = ((double)r - minRating)/(maxRating - minRating)
2568             * (h-vMargin-squareSize/8-1) + vMargin;
2569         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2570         if(strstr(seekAdList[i], " u ")) color = 1;
2571         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2572            !strstr(seekAdList[i], "bullet") &&
2573            !strstr(seekAdList[i], "blitz") &&
2574            !strstr(seekAdList[i], "standard") ) color = 2;
2575         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2576         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2577 }
2578
2579 void
2580 PlotSingleSeekAd (int i)
2581 {
2582         PlotSeekAd(i);
2583 }
2584
2585 void
2586 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2587 {
2588         char buf[MSG_SIZ], *ext = "";
2589         VariantClass v = StringToVariant(type);
2590         if(strstr(type, "wild")) {
2591             ext = type + 4; // append wild number
2592             if(v == VariantFischeRandom) type = "chess960"; else
2593             if(v == VariantLoadable) type = "setup"; else
2594             type = VariantName(v);
2595         }
2596         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2597         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2598             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2599             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2600             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2601             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2602             seekNrList[nrOfSeekAds] = nr;
2603             zList[nrOfSeekAds] = 0;
2604             seekAdList[nrOfSeekAds++] = StrSave(buf);
2605             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2606         }
2607 }
2608
2609 void
2610 EraseSeekDot (int i)
2611 {
2612     int x = xList[i], y = yList[i], d=squareSize/4, k;
2613     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2614     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2615     // now replot every dot that overlapped
2616     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2617         int xx = xList[k], yy = yList[k];
2618         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2619             DrawSeekDot(xx, yy, colorList[k]);
2620     }
2621 }
2622
2623 void
2624 RemoveSeekAd (int nr)
2625 {
2626         int i;
2627         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2628             EraseSeekDot(i);
2629             if(seekAdList[i]) free(seekAdList[i]);
2630             seekAdList[i] = seekAdList[--nrOfSeekAds];
2631             seekNrList[i] = seekNrList[nrOfSeekAds];
2632             ratingList[i] = ratingList[nrOfSeekAds];
2633             colorList[i]  = colorList[nrOfSeekAds];
2634             tcList[i] = tcList[nrOfSeekAds];
2635             xList[i]  = xList[nrOfSeekAds];
2636             yList[i]  = yList[nrOfSeekAds];
2637             zList[i]  = zList[nrOfSeekAds];
2638             seekAdList[nrOfSeekAds] = NULL;
2639             break;
2640         }
2641 }
2642
2643 Boolean
2644 MatchSoughtLine (char *line)
2645 {
2646     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2647     int nr, base, inc, u=0; char dummy;
2648
2649     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2650        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2651        (u=1) &&
2652        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2653         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2654         // match: compact and save the line
2655         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2656         return TRUE;
2657     }
2658     return FALSE;
2659 }
2660
2661 int
2662 DrawSeekGraph ()
2663 {
2664     int i;
2665     if(!seekGraphUp) return FALSE;
2666     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2667     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2668
2669     DrawSeekBackground(0, 0, w, h);
2670     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2671     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2672     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2673         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2674         yy = h-1-yy;
2675         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2676         if(i%500 == 0) {
2677             char buf[MSG_SIZ];
2678             snprintf(buf, MSG_SIZ, "%d", i);
2679             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2680         }
2681     }
2682     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2683     for(i=1; i<100; i+=(i<10?1:5)) {
2684         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2685         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2686         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2687             char buf[MSG_SIZ];
2688             snprintf(buf, MSG_SIZ, "%d", i);
2689             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2690         }
2691     }
2692     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2693     return TRUE;
2694 }
2695
2696 int
2697 SeekGraphClick (ClickType click, int x, int y, int moving)
2698 {
2699     static int lastDown = 0, displayed = 0, lastSecond;
2700     if(y < 0) return FALSE;
2701     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2702         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2703         if(!seekGraphUp) return FALSE;
2704         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2705         DrawPosition(TRUE, NULL);
2706         return TRUE;
2707     }
2708     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2709         if(click == Release || moving) return FALSE;
2710         nrOfSeekAds = 0;
2711         soughtPending = TRUE;
2712         SendToICS(ics_prefix);
2713         SendToICS("sought\n"); // should this be "sought all"?
2714     } else { // issue challenge based on clicked ad
2715         int dist = 10000; int i, closest = 0, second = 0;
2716         for(i=0; i<nrOfSeekAds; i++) {
2717             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2718             if(d < dist) { dist = d; closest = i; }
2719             second += (d - zList[i] < 120); // count in-range ads
2720             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2721         }
2722         if(dist < 120) {
2723             char buf[MSG_SIZ];
2724             second = (second > 1);
2725             if(displayed != closest || second != lastSecond) {
2726                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2727                 lastSecond = second; displayed = closest;
2728             }
2729             if(click == Press) {
2730                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2731                 lastDown = closest;
2732                 return TRUE;
2733             } // on press 'hit', only show info
2734             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2735             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2736             SendToICS(ics_prefix);
2737             SendToICS(buf);
2738             return TRUE; // let incoming board of started game pop down the graph
2739         } else if(click == Release) { // release 'miss' is ignored
2740             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2741             if(moving == 2) { // right up-click
2742                 nrOfSeekAds = 0; // refresh graph
2743                 soughtPending = TRUE;
2744                 SendToICS(ics_prefix);
2745                 SendToICS("sought\n"); // should this be "sought all"?
2746             }
2747             return TRUE;
2748         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2749         // press miss or release hit 'pop down' seek graph
2750         seekGraphUp = FALSE;
2751         DrawPosition(TRUE, NULL);
2752     }
2753     return TRUE;
2754 }
2755
2756 void
2757 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2758 {
2759 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2760 #define STARTED_NONE 0
2761 #define STARTED_MOVES 1
2762 #define STARTED_BOARD 2
2763 #define STARTED_OBSERVE 3
2764 #define STARTED_HOLDINGS 4
2765 #define STARTED_CHATTER 5
2766 #define STARTED_COMMENT 6
2767 #define STARTED_MOVES_NOHIDE 7
2768
2769     static int started = STARTED_NONE;
2770     static char parse[20000];
2771     static int parse_pos = 0;
2772     static char buf[BUF_SIZE + 1];
2773     static int firstTime = TRUE, intfSet = FALSE;
2774     static ColorClass prevColor = ColorNormal;
2775     static int savingComment = FALSE;
2776     static int cmatch = 0; // continuation sequence match
2777     char *bp;
2778     char str[MSG_SIZ];
2779     int i, oldi;
2780     int buf_len;
2781     int next_out;
2782     int tkind;
2783     int backup;    /* [DM] For zippy color lines */
2784     char *p;
2785     char talker[MSG_SIZ]; // [HGM] chat
2786     int channel;
2787
2788     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2789
2790     if (appData.debugMode) {
2791       if (!error) {
2792         fprintf(debugFP, "<ICS: ");
2793         show_bytes(debugFP, data, count);
2794         fprintf(debugFP, "\n");
2795       }
2796     }
2797
2798     if (appData.debugMode) { int f = forwardMostMove;
2799         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2800                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2801                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2802     }
2803     if (count > 0) {
2804         /* If last read ended with a partial line that we couldn't parse,
2805            prepend it to the new read and try again. */
2806         if (leftover_len > 0) {
2807             for (i=0; i<leftover_len; i++)
2808               buf[i] = buf[leftover_start + i];
2809         }
2810
2811     /* copy new characters into the buffer */
2812     bp = buf + leftover_len;
2813     buf_len=leftover_len;
2814     for (i=0; i<count; i++)
2815     {
2816         // ignore these
2817         if (data[i] == '\r')
2818             continue;
2819
2820         // join lines split by ICS?
2821         if (!appData.noJoin)
2822         {
2823             /*
2824                 Joining just consists of finding matches against the
2825                 continuation sequence, and discarding that sequence
2826                 if found instead of copying it.  So, until a match
2827                 fails, there's nothing to do since it might be the
2828                 complete sequence, and thus, something we don't want
2829                 copied.
2830             */
2831             if (data[i] == cont_seq[cmatch])
2832             {
2833                 cmatch++;
2834                 if (cmatch == strlen(cont_seq))
2835                 {
2836                     cmatch = 0; // complete match.  just reset the counter
2837
2838                     /*
2839                         it's possible for the ICS to not include the space
2840                         at the end of the last word, making our [correct]
2841                         join operation fuse two separate words.  the server
2842                         does this when the space occurs at the width setting.
2843                     */
2844                     if (!buf_len || buf[buf_len-1] != ' ')
2845                     {
2846                         *bp++ = ' ';
2847                         buf_len++;
2848                     }
2849                 }
2850                 continue;
2851             }
2852             else if (cmatch)
2853             {
2854                 /*
2855                     match failed, so we have to copy what matched before
2856                     falling through and copying this character.  In reality,
2857                     this will only ever be just the newline character, but
2858                     it doesn't hurt to be precise.
2859                 */
2860                 strncpy(bp, cont_seq, cmatch);
2861                 bp += cmatch;
2862                 buf_len += cmatch;
2863                 cmatch = 0;
2864             }
2865         }
2866
2867         // copy this char
2868         *bp++ = data[i];
2869         buf_len++;
2870     }
2871
2872         buf[buf_len] = NULLCHAR;
2873 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2874         next_out = 0;
2875         leftover_start = 0;
2876
2877         i = 0;
2878         while (i < buf_len) {
2879             /* Deal with part of the TELNET option negotiation
2880                protocol.  We refuse to do anything beyond the
2881                defaults, except that we allow the WILL ECHO option,
2882                which ICS uses to turn off password echoing when we are
2883                directly connected to it.  We reject this option
2884                if localLineEditing mode is on (always on in xboard)
2885                and we are talking to port 23, which might be a real
2886                telnet server that will try to keep WILL ECHO on permanently.
2887              */
2888             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2889                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2890                 unsigned char option;
2891                 oldi = i;
2892                 switch ((unsigned char) buf[++i]) {
2893                   case TN_WILL:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<WILL ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       case TN_ECHO:
2898                         if (appData.debugMode)
2899                           fprintf(debugFP, "ECHO ");
2900                         /* Reply only if this is a change, according
2901                            to the protocol rules. */
2902                         if (remoteEchoOption) break;
2903                         if (appData.localLineEditing &&
2904                             atoi(appData.icsPort) == TN_PORT) {
2905                             TelnetRequest(TN_DONT, TN_ECHO);
2906                         } else {
2907                             EchoOff();
2908                             TelnetRequest(TN_DO, TN_ECHO);
2909                             remoteEchoOption = TRUE;
2910                         }
2911                         break;
2912                       default:
2913                         if (appData.debugMode)
2914                           fprintf(debugFP, "%d ", option);
2915                         /* Whatever this is, we don't want it. */
2916                         TelnetRequest(TN_DONT, option);
2917                         break;
2918                     }
2919                     break;
2920                   case TN_WONT:
2921                     if (appData.debugMode)
2922                       fprintf(debugFP, "\n<WONT ");
2923                     switch (option = (unsigned char) buf[++i]) {
2924                       case TN_ECHO:
2925                         if (appData.debugMode)
2926                           fprintf(debugFP, "ECHO ");
2927                         /* Reply only if this is a change, according
2928                            to the protocol rules. */
2929                         if (!remoteEchoOption) break;
2930                         EchoOn();
2931                         TelnetRequest(TN_DONT, TN_ECHO);
2932                         remoteEchoOption = FALSE;
2933                         break;
2934                       default:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "%d ", (unsigned char) option);
2937                         /* Whatever this is, it must already be turned
2938                            off, because we never agree to turn on
2939                            anything non-default, so according to the
2940                            protocol rules, we don't reply. */
2941                         break;
2942                     }
2943                     break;
2944                   case TN_DO:
2945                     if (appData.debugMode)
2946                       fprintf(debugFP, "\n<DO ");
2947                     switch (option = (unsigned char) buf[++i]) {
2948                       default:
2949                         /* Whatever this is, we refuse to do it. */
2950                         if (appData.debugMode)
2951                           fprintf(debugFP, "%d ", option);
2952                         TelnetRequest(TN_WONT, option);
2953                         break;
2954                     }
2955                     break;
2956                   case TN_DONT:
2957                     if (appData.debugMode)
2958                       fprintf(debugFP, "\n<DONT ");
2959                     switch (option = (unsigned char) buf[++i]) {
2960                       default:
2961                         if (appData.debugMode)
2962                           fprintf(debugFP, "%d ", option);
2963                         /* Whatever this is, we are already not doing
2964                            it, because we never agree to do anything
2965                            non-default, so according to the protocol
2966                            rules, we don't reply. */
2967                         break;
2968                     }
2969                     break;
2970                   case TN_IAC:
2971                     if (appData.debugMode)
2972                       fprintf(debugFP, "\n<IAC ");
2973                     /* Doubled IAC; pass it through */
2974                     i--;
2975                     break;
2976                   default:
2977                     if (appData.debugMode)
2978                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2979                     /* Drop all other telnet commands on the floor */
2980                     break;
2981                 }
2982                 if (oldi > next_out)
2983                   SendToPlayer(&buf[next_out], oldi - next_out);
2984                 if (++i > next_out)
2985                   next_out = i;
2986                 continue;
2987             }
2988
2989             /* OK, this at least will *usually* work */
2990             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2991                 loggedOn = TRUE;
2992             }
2993
2994             if (loggedOn && !intfSet) {
2995                 if (ics_type == ICS_ICC) {
2996                   snprintf(str, MSG_SIZ,
2997                           "/set-quietly interface %s\n/set-quietly style 12\n",
2998                           programVersion);
2999                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3001                 } else if (ics_type == ICS_CHESSNET) {
3002                   snprintf(str, MSG_SIZ, "/style 12\n");
3003                 } else {
3004                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3005                   strcat(str, programVersion);
3006                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3007                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3008                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3009 #ifdef WIN32
3010                   strcat(str, "$iset nohighlight 1\n");
3011 #endif
3012                   strcat(str, "$iset lock 1\n$style 12\n");
3013                 }
3014                 SendToICS(str);
3015                 NotifyFrontendLogin();
3016                 intfSet = TRUE;
3017             }
3018
3019             if (started == STARTED_COMMENT) {
3020                 /* Accumulate characters in comment */
3021                 parse[parse_pos++] = buf[i];
3022                 if (buf[i] == '\n') {
3023                     parse[parse_pos] = NULLCHAR;
3024                     if(chattingPartner>=0) {
3025                         char mess[MSG_SIZ];
3026                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3027                         OutputChatMessage(chattingPartner, mess);
3028                         chattingPartner = -1;
3029                         next_out = i+1; // [HGM] suppress printing in ICS window
3030                     } else
3031                     if(!suppressKibitz) // [HGM] kibitz
3032                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3033                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3034                         int nrDigit = 0, nrAlph = 0, j;
3035                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3036                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3037                         parse[parse_pos] = NULLCHAR;
3038                         // try to be smart: if it does not look like search info, it should go to
3039                         // ICS interaction window after all, not to engine-output window.
3040                         for(j=0; j<parse_pos; j++) { // count letters and digits
3041                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3042                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3043                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3044                         }
3045                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3046                             int depth=0; float score;
3047                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3048                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3049                                 pvInfoList[forwardMostMove-1].depth = depth;
3050                                 pvInfoList[forwardMostMove-1].score = 100*score;
3051                             }
3052                             OutputKibitz(suppressKibitz, parse);
3053                         } else {
3054                             char tmp[MSG_SIZ];
3055                             if(gameMode == IcsObserving) // restore original ICS messages
3056                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3057                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3058                             else
3059                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3060                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3061                             SendToPlayer(tmp, strlen(tmp));
3062                         }
3063                         next_out = i+1; // [HGM] suppress printing in ICS window
3064                     }
3065                     started = STARTED_NONE;
3066                 } else {
3067                     /* Don't match patterns against characters in comment */
3068                     i++;
3069                     continue;
3070                 }
3071             }
3072             if (started == STARTED_CHATTER) {
3073                 if (buf[i] != '\n') {
3074                     /* Don't match patterns against characters in chatter */
3075                     i++;
3076                     continue;
3077                 }
3078                 started = STARTED_NONE;
3079                 if(suppressKibitz) next_out = i+1;
3080             }
3081
3082             /* Kludge to deal with rcmd protocol */
3083             if (firstTime && looking_at(buf, &i, "\001*")) {
3084                 DisplayFatalError(&buf[1], 0, 1);
3085                 continue;
3086             } else {
3087                 firstTime = FALSE;
3088             }
3089
3090             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3091                 ics_type = ICS_ICC;
3092                 ics_prefix = "/";
3093                 if (appData.debugMode)
3094                   fprintf(debugFP, "ics_type %d\n", ics_type);
3095                 continue;
3096             }
3097             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3098                 ics_type = ICS_FICS;
3099                 ics_prefix = "$";
3100                 if (appData.debugMode)
3101                   fprintf(debugFP, "ics_type %d\n", ics_type);
3102                 continue;
3103             }
3104             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3105                 ics_type = ICS_CHESSNET;
3106                 ics_prefix = "/";
3107                 if (appData.debugMode)
3108                   fprintf(debugFP, "ics_type %d\n", ics_type);
3109                 continue;
3110             }
3111
3112             if (!loggedOn &&
3113                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3114                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3115                  looking_at(buf, &i, "will be \"*\""))) {
3116               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3117               continue;
3118             }
3119
3120             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3121               char buf[MSG_SIZ];
3122               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3123               DisplayIcsInteractionTitle(buf);
3124               have_set_title = TRUE;
3125             }
3126
3127             /* skip finger notes */
3128             if (started == STARTED_NONE &&
3129                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3130                  (buf[i] == '1' && buf[i+1] == '0')) &&
3131                 buf[i+2] == ':' && buf[i+3] == ' ') {
3132               started = STARTED_CHATTER;
3133               i += 3;
3134               continue;
3135             }
3136
3137             oldi = i;
3138             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3139             if(appData.seekGraph) {
3140                 if(soughtPending && MatchSoughtLine(buf+i)) {
3141                     i = strstr(buf+i, "rated") - buf;
3142                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143                     next_out = leftover_start = i;
3144                     started = STARTED_CHATTER;
3145                     suppressKibitz = TRUE;
3146                     continue;
3147                 }
3148                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3149                         && looking_at(buf, &i, "* ads displayed")) {
3150                     soughtPending = FALSE;
3151                     seekGraphUp = TRUE;
3152                     DrawSeekGraph();
3153                     continue;
3154                 }
3155                 if(appData.autoRefresh) {
3156                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3157                         int s = (ics_type == ICS_ICC); // ICC format differs
3158                         if(seekGraphUp)
3159                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3160                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3161                         looking_at(buf, &i, "*% "); // eat prompt
3162                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3163                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3164                         next_out = i; // suppress
3165                         continue;
3166                     }
3167                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3168                         char *p = star_match[0];
3169                         while(*p) {
3170                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3171                             while(*p && *p++ != ' '); // next
3172                         }
3173                         looking_at(buf, &i, "*% "); // eat prompt
3174                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175                         next_out = i;
3176                         continue;
3177                     }
3178                 }
3179             }
3180
3181             /* skip formula vars */
3182             if (started == STARTED_NONE &&
3183                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3184               started = STARTED_CHATTER;
3185               i += 3;
3186               continue;
3187             }
3188
3189             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3190             if (appData.autoKibitz && started == STARTED_NONE &&
3191                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3192                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3193                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3194                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3195                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3196                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3197                         suppressKibitz = TRUE;
3198                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199                         next_out = i;
3200                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3201                                 && (gameMode == IcsPlayingWhite)) ||
3202                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3203                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3204                             started = STARTED_CHATTER; // own kibitz we simply discard
3205                         else {
3206                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3207                             parse_pos = 0; parse[0] = NULLCHAR;
3208                             savingComment = TRUE;
3209                             suppressKibitz = gameMode != IcsObserving ? 2 :
3210                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3211                         }
3212                         continue;
3213                 } else
3214                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3215                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3216                          && atoi(star_match[0])) {
3217                     // suppress the acknowledgements of our own autoKibitz
3218                     char *p;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3221                     SendToPlayer(star_match[0], strlen(star_match[0]));
3222                     if(looking_at(buf, &i, "*% ")) // eat prompt
3223                         suppressKibitz = FALSE;
3224                     next_out = i;
3225                     continue;
3226                 }
3227             } // [HGM] kibitz: end of patch
3228
3229             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3230
3231             // [HGM] chat: intercept tells by users for which we have an open chat window
3232             channel = -1;
3233             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3234                                            looking_at(buf, &i, "* whispers:") ||
3235                                            looking_at(buf, &i, "* kibitzes:") ||
3236                                            looking_at(buf, &i, "* shouts:") ||
3237                                            looking_at(buf, &i, "* c-shouts:") ||
3238                                            looking_at(buf, &i, "--> * ") ||
3239                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3240                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3241                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3242                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3243                 int p;
3244                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3245                 chattingPartner = -1;
3246
3247                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3248                 for(p=0; p<MAX_CHAT; p++) {
3249                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3250                     talker[0] = '['; strcat(talker, "] ");
3251                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3252                     chattingPartner = p; break;
3253                     }
3254                 } else
3255                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3256                 for(p=0; p<MAX_CHAT; p++) {
3257                     if(!strcmp("kibitzes", chatPartner[p])) {
3258                         talker[0] = '['; strcat(talker, "] ");
3259                         chattingPartner = p; break;
3260                     }
3261                 } else
3262                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3263                 for(p=0; p<MAX_CHAT; p++) {
3264                     if(!strcmp("whispers", chatPartner[p])) {
3265                         talker[0] = '['; strcat(talker, "] ");
3266                         chattingPartner = p; break;
3267                     }
3268                 } else
3269                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3270                   if(buf[i-8] == '-' && buf[i-3] == 't')
3271                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3272                     if(!strcmp("c-shouts", chatPartner[p])) {
3273                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3274                         chattingPartner = p; break;
3275                     }
3276                   }
3277                   if(chattingPartner < 0)
3278                   for(p=0; p<MAX_CHAT; p++) {
3279                     if(!strcmp("shouts", chatPartner[p])) {
3280                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3281                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3282                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3283                         chattingPartner = p; break;
3284                     }
3285                   }
3286                 }
3287                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3288                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3289                     talker[0] = 0; Colorize(ColorTell, FALSE);
3290                     chattingPartner = p; break;
3291                 }
3292                 if(chattingPartner<0) i = oldi; else {
3293                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3294                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3295                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3296                     started = STARTED_COMMENT;
3297                     parse_pos = 0; parse[0] = NULLCHAR;
3298                     savingComment = 3 + chattingPartner; // counts as TRUE
3299                     suppressKibitz = TRUE;
3300                     continue;
3301                 }
3302             } // [HGM] chat: end of patch
3303
3304           backup = i;
3305             if (appData.zippyTalk || appData.zippyPlay) {
3306                 /* [DM] Backup address for color zippy lines */
3307 #if ZIPPY
3308                if (loggedOn == TRUE)
3309                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3310                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3311 #endif
3312             } // [DM] 'else { ' deleted
3313                 if (
3314                     /* Regular tells and says */
3315                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3316                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3317                     looking_at(buf, &i, "* says: ") ||
3318                     /* Don't color "message" or "messages" output */
3319                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3320                     looking_at(buf, &i, "*. * at *:*: ") ||
3321                     looking_at(buf, &i, "--* (*:*): ") ||
3322                     /* Message notifications (same color as tells) */
3323                     looking_at(buf, &i, "* has left a message ") ||
3324                     looking_at(buf, &i, "* just sent you a message:\n") ||
3325                     /* Whispers and kibitzes */
3326                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3327                     looking_at(buf, &i, "* kibitzes: ") ||
3328                     /* Channel tells */
3329                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3330
3331                   if (tkind == 1 && strchr(star_match[0], ':')) {
3332                       /* Avoid "tells you:" spoofs in channels */
3333                      tkind = 3;
3334                   }
3335                   if (star_match[0][0] == NULLCHAR ||
3336                       strchr(star_match[0], ' ') ||
3337                       (tkind == 3 && strchr(star_match[1], ' '))) {
3338                     /* Reject bogus matches */
3339                     i = oldi;
3340                   } else {
3341                     if (appData.colorize) {
3342                       if (oldi > next_out) {
3343                         SendToPlayer(&buf[next_out], oldi - next_out);
3344                         next_out = oldi;
3345                       }
3346                       switch (tkind) {
3347                       case 1:
3348                         Colorize(ColorTell, FALSE);
3349                         curColor = ColorTell;
3350                         break;
3351                       case 2:
3352                         Colorize(ColorKibitz, FALSE);
3353                         curColor = ColorKibitz;
3354                         break;
3355                       case 3:
3356                         p = strrchr(star_match[1], '(');
3357                         if (p == NULL) {
3358                           p = star_match[1];
3359                         } else {
3360                           p++;
3361                         }
3362                         if (atoi(p) == 1) {
3363                           Colorize(ColorChannel1, FALSE);
3364                           curColor = ColorChannel1;
3365                         } else {
3366                           Colorize(ColorChannel, FALSE);
3367                           curColor = ColorChannel;
3368                         }
3369                         break;
3370                       case 5:
3371                         curColor = ColorNormal;
3372                         break;
3373                       }
3374                     }
3375                     if (started == STARTED_NONE && appData.autoComment &&
3376                         (gameMode == IcsObserving ||
3377                          gameMode == IcsPlayingWhite ||
3378                          gameMode == IcsPlayingBlack)) {
3379                       parse_pos = i - oldi;
3380                       memcpy(parse, &buf[oldi], parse_pos);
3381                       parse[parse_pos] = NULLCHAR;
3382                       started = STARTED_COMMENT;
3383                       savingComment = TRUE;
3384                     } else {
3385                       started = STARTED_CHATTER;
3386                       savingComment = FALSE;
3387                     }
3388                     loggedOn = TRUE;
3389                     continue;
3390                   }
3391                 }
3392
3393                 if (looking_at(buf, &i, "* s-shouts: ") ||
3394                     looking_at(buf, &i, "* c-shouts: ")) {
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorSShout, FALSE);
3401                         curColor = ColorSShout;
3402                     }
3403                     loggedOn = TRUE;
3404                     started = STARTED_CHATTER;
3405                     continue;
3406                 }
3407
3408                 if (looking_at(buf, &i, "--->")) {
3409                     loggedOn = TRUE;
3410                     continue;
3411                 }
3412
3413                 if (looking_at(buf, &i, "* shouts: ") ||
3414                     looking_at(buf, &i, "--> ")) {
3415                     if (appData.colorize) {
3416                         if (oldi > next_out) {
3417                             SendToPlayer(&buf[next_out], oldi - next_out);
3418                             next_out = oldi;
3419                         }
3420                         Colorize(ColorShout, FALSE);
3421                         curColor = ColorShout;
3422                     }
3423                     loggedOn = TRUE;
3424                     started = STARTED_CHATTER;
3425                     continue;
3426                 }
3427
3428                 if (looking_at( buf, &i, "Challenge:")) {
3429                     if (appData.colorize) {
3430                         if (oldi > next_out) {
3431                             SendToPlayer(&buf[next_out], oldi - next_out);
3432                             next_out = oldi;
3433                         }
3434                         Colorize(ColorChallenge, FALSE);
3435                         curColor = ColorChallenge;
3436                     }
3437                     loggedOn = TRUE;
3438                     continue;
3439                 }
3440
3441                 if (looking_at(buf, &i, "* offers you") ||
3442                     looking_at(buf, &i, "* offers to be") ||
3443                     looking_at(buf, &i, "* would like to") ||
3444                     looking_at(buf, &i, "* requests to") ||
3445                     looking_at(buf, &i, "Your opponent offers") ||
3446                     looking_at(buf, &i, "Your opponent requests")) {
3447
3448                     if (appData.colorize) {
3449                         if (oldi > next_out) {
3450                             SendToPlayer(&buf[next_out], oldi - next_out);
3451                             next_out = oldi;
3452                         }
3453                         Colorize(ColorRequest, FALSE);
3454                         curColor = ColorRequest;
3455                     }
3456                     continue;
3457                 }
3458
3459                 if (looking_at(buf, &i, "* (*) seeking")) {
3460                     if (appData.colorize) {
3461                         if (oldi > next_out) {
3462                             SendToPlayer(&buf[next_out], oldi - next_out);
3463                             next_out = oldi;
3464                         }
3465                         Colorize(ColorSeek, FALSE);
3466                         curColor = ColorSeek;
3467                     }
3468                     continue;
3469             }
3470
3471           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3472
3473             if (looking_at(buf, &i, "\\   ")) {
3474                 if (prevColor != ColorNormal) {
3475                     if (oldi > next_out) {
3476                         SendToPlayer(&buf[next_out], oldi - next_out);
3477                         next_out = oldi;
3478                     }
3479                     Colorize(prevColor, TRUE);
3480                     curColor = prevColor;
3481                 }
3482                 if (savingComment) {
3483                     parse_pos = i - oldi;
3484                     memcpy(parse, &buf[oldi], parse_pos);
3485                     parse[parse_pos] = NULLCHAR;
3486                     started = STARTED_COMMENT;
3487                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3488                         chattingPartner = savingComment - 3; // kludge to remember the box
3489                 } else {
3490                     started = STARTED_CHATTER;
3491                 }
3492                 continue;
3493             }
3494
3495             if (looking_at(buf, &i, "Black Strength :") ||
3496                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3497                 looking_at(buf, &i, "<10>") ||
3498                 looking_at(buf, &i, "#@#")) {
3499                 /* Wrong board style */
3500                 loggedOn = TRUE;
3501                 SendToICS(ics_prefix);
3502                 SendToICS("set style 12\n");
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (looking_at(buf, &i, "login:")) {
3509               if (!have_sent_ICS_logon) {
3510                 if(ICSInitScript())
3511                   have_sent_ICS_logon = 1;
3512                 else // no init script was found
3513                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3514               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3515                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3516               }
3517                 continue;
3518             }
3519
3520             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3521                 (looking_at(buf, &i, "\n<12> ") ||
3522                  looking_at(buf, &i, "<12> "))) {
3523                 loggedOn = TRUE;
3524                 if (oldi > next_out) {
3525                     SendToPlayer(&buf[next_out], oldi - next_out);
3526                 }
3527                 next_out = i;
3528                 started = STARTED_BOARD;
3529                 parse_pos = 0;
3530                 continue;
3531             }
3532
3533             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3534                 looking_at(buf, &i, "<b1> ")) {
3535                 if (oldi > next_out) {
3536                     SendToPlayer(&buf[next_out], oldi - next_out);
3537                 }
3538                 next_out = i;
3539                 started = STARTED_HOLDINGS;
3540                 parse_pos = 0;
3541                 continue;
3542             }
3543
3544             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3545                 loggedOn = TRUE;
3546                 /* Header for a move list -- first line */
3547
3548                 switch (ics_getting_history) {
3549                   case H_FALSE:
3550                     switch (gameMode) {
3551                       case IcsIdle:
3552                       case BeginningOfGame:
3553                         /* User typed "moves" or "oldmoves" while we
3554                            were idle.  Pretend we asked for these
3555                            moves and soak them up so user can step
3556                            through them and/or save them.
3557                            */
3558                         Reset(FALSE, TRUE);
3559                         gameMode = IcsObserving;
3560                         ModeHighlight();
3561                         ics_gamenum = -1;
3562                         ics_getting_history = H_GOT_UNREQ_HEADER;
3563                         break;
3564                       case EditGame: /*?*/
3565                       case EditPosition: /*?*/
3566                         /* Should above feature work in these modes too? */
3567                         /* For now it doesn't */
3568                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3569                         break;
3570                       default:
3571                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3572                         break;
3573                     }
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Is this the right one? */
3577                     if (gameInfo.white && gameInfo.black &&
3578                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3579                         strcmp(gameInfo.black, star_match[2]) == 0) {
3580                         /* All is well */
3581                         ics_getting_history = H_GOT_REQ_HEADER;
3582                     }
3583                     break;
3584                   case H_GOT_REQ_HEADER:
3585                   case H_GOT_UNREQ_HEADER:
3586                   case H_GOT_UNWANTED_HEADER:
3587                   case H_GETTING_MOVES:
3588                     /* Should not happen */
3589                     DisplayError(_("Error gathering move list: two headers"), 0);
3590                     ics_getting_history = H_FALSE;
3591                     break;
3592                 }
3593
3594                 /* Save player ratings into gameInfo if needed */
3595                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3596                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3597                     (gameInfo.whiteRating == -1 ||
3598                      gameInfo.blackRating == -1)) {
3599
3600                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3601                     gameInfo.blackRating = string_to_rating(star_match[3]);
3602                     if (appData.debugMode)
3603                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3604                               gameInfo.whiteRating, gameInfo.blackRating);
3605                 }
3606                 continue;
3607             }
3608
3609             if (looking_at(buf, &i,
3610               "* * match, initial time: * minute*, increment: * second")) {
3611                 /* Header for a move list -- second line */
3612                 /* Initial board will follow if this is a wild game */
3613                 if (gameInfo.event != NULL) free(gameInfo.event);
3614                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3615                 gameInfo.event = StrSave(str);
3616                 /* [HGM] we switched variant. Translate boards if needed. */
3617                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3618                 continue;
3619             }
3620
3621             if (looking_at(buf, &i, "Move  ")) {
3622                 /* Beginning of a move list */
3623                 switch (ics_getting_history) {
3624                   case H_FALSE:
3625                     /* Normally should not happen */
3626                     /* Maybe user hit reset while we were parsing */
3627                     break;
3628                   case H_REQUESTED:
3629                     /* Happens if we are ignoring a move list that is not
3630                      * the one we just requested.  Common if the user
3631                      * tries to observe two games without turning off
3632                      * getMoveList */
3633                     break;
3634                   case H_GETTING_MOVES:
3635                     /* Should not happen */
3636                     DisplayError(_("Error gathering move list: nested"), 0);
3637                     ics_getting_history = H_FALSE;
3638                     break;
3639                   case H_GOT_REQ_HEADER:
3640                     ics_getting_history = H_GETTING_MOVES;
3641                     started = STARTED_MOVES;
3642                     parse_pos = 0;
3643                     if (oldi > next_out) {
3644                         SendToPlayer(&buf[next_out], oldi - next_out);
3645                     }
3646                     break;
3647                   case H_GOT_UNREQ_HEADER:
3648                     ics_getting_history = H_GETTING_MOVES;
3649                     started = STARTED_MOVES_NOHIDE;
3650                     parse_pos = 0;
3651                     break;
3652                   case H_GOT_UNWANTED_HEADER:
3653                     ics_getting_history = H_FALSE;
3654                     break;
3655                 }
3656                 continue;
3657             }
3658
3659             if (looking_at(buf, &i, "% ") ||
3660                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3661                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3662                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3663                     soughtPending = FALSE;
3664                     seekGraphUp = TRUE;
3665                     DrawSeekGraph();
3666                 }
3667                 if(suppressKibitz) next_out = i;
3668                 savingComment = FALSE;
3669                 suppressKibitz = 0;
3670                 switch (started) {
3671                   case STARTED_MOVES:
3672                   case STARTED_MOVES_NOHIDE:
3673                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3674                     parse[parse_pos + i - oldi] = NULLCHAR;
3675                     ParseGameHistory(parse);
3676 #if ZIPPY
3677                     if (appData.zippyPlay && first.initDone) {
3678                         FeedMovesToProgram(&first, forwardMostMove);
3679                         if (gameMode == IcsPlayingWhite) {
3680                             if (WhiteOnMove(forwardMostMove)) {
3681                                 if (first.sendTime) {
3682                                   if (first.useColors) {
3683                                     SendToProgram("black\n", &first);
3684                                   }
3685                                   SendTimeRemaining(&first, TRUE);
3686                                 }
3687                                 if (first.useColors) {
3688                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3689                                 }
3690                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3691                                 first.maybeThinking = TRUE;
3692                             } else {
3693                                 if (first.usePlayother) {
3694                                   if (first.sendTime) {
3695                                     SendTimeRemaining(&first, TRUE);
3696                                   }
3697                                   SendToProgram("playother\n", &first);
3698                                   firstMove = FALSE;
3699                                 } else {
3700                                   firstMove = TRUE;
3701                                 }
3702                             }
3703                         } else if (gameMode == IcsPlayingBlack) {
3704                             if (!WhiteOnMove(forwardMostMove)) {
3705                                 if (first.sendTime) {
3706                                   if (first.useColors) {
3707                                     SendToProgram("white\n", &first);
3708                                   }
3709                                   SendTimeRemaining(&first, FALSE);
3710                                 }
3711                                 if (first.useColors) {
3712                                   SendToProgram("black\n", &first);
3713                                 }
3714                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3715                                 first.maybeThinking = TRUE;
3716                             } else {
3717                                 if (first.usePlayother) {
3718                                   if (first.sendTime) {
3719                                     SendTimeRemaining(&first, FALSE);
3720                                   }
3721                                   SendToProgram("playother\n", &first);
3722                                   firstMove = FALSE;
3723                                 } else {
3724                                   firstMove = TRUE;
3725                                 }
3726                             }
3727                         }
3728                     }
3729 #endif
3730                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3731                         /* Moves came from oldmoves or moves command
3732                            while we weren't doing anything else.
3733                            */
3734                         currentMove = forwardMostMove;
3735                         ClearHighlights();/*!!could figure this out*/
3736                         flipView = appData.flipView;
3737                         DrawPosition(TRUE, boards[currentMove]);
3738                         DisplayBothClocks();
3739                         snprintf(str, MSG_SIZ, "%s %s %s",
3740                                 gameInfo.white, _("vs."),  gameInfo.black);
3741                         DisplayTitle(str);
3742                         gameMode = IcsIdle;
3743                     } else {
3744                         /* Moves were history of an active game */
3745                         if (gameInfo.resultDetails != NULL) {
3746                             free(gameInfo.resultDetails);
3747                             gameInfo.resultDetails = NULL;
3748                         }
3749                     }
3750                     HistorySet(parseList, backwardMostMove,
3751                                forwardMostMove, currentMove-1);
3752                     DisplayMove(currentMove - 1);
3753                     if (started == STARTED_MOVES) next_out = i;
3754                     started = STARTED_NONE;
3755                     ics_getting_history = H_FALSE;
3756                     break;
3757
3758                   case STARTED_OBSERVE:
3759                     started = STARTED_NONE;
3760                     SendToICS(ics_prefix);
3761                     SendToICS("refresh\n");
3762                     break;
3763
3764                   default:
3765                     break;
3766                 }
3767                 if(bookHit) { // [HGM] book: simulate book reply
3768                     static char bookMove[MSG_SIZ]; // a bit generous?
3769
3770                     programStats.nodes = programStats.depth = programStats.time =
3771                     programStats.score = programStats.got_only_move = 0;
3772                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3773
3774                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3775                     strcat(bookMove, bookHit);
3776                     HandleMachineMove(bookMove, &first);
3777                 }
3778                 continue;
3779             }
3780
3781             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3782                  started == STARTED_HOLDINGS ||
3783                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3784                 /* Accumulate characters in move list or board */
3785                 parse[parse_pos++] = buf[i];
3786             }
3787
3788             /* Start of game messages.  Mostly we detect start of game
3789                when the first board image arrives.  On some versions
3790                of the ICS, though, we need to do a "refresh" after starting
3791                to observe in order to get the current board right away. */
3792             if (looking_at(buf, &i, "Adding game * to observation list")) {
3793                 started = STARTED_OBSERVE;
3794                 continue;
3795             }
3796
3797             /* Handle auto-observe */
3798             if (appData.autoObserve &&
3799                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3800                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3801                 char *player;
3802                 /* Choose the player that was highlighted, if any. */
3803                 if (star_match[0][0] == '\033' ||
3804                     star_match[1][0] != '\033') {
3805                     player = star_match[0];
3806                 } else {
3807                     player = star_match[2];
3808                 }
3809                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3810                         ics_prefix, StripHighlightAndTitle(player));
3811                 SendToICS(str);
3812
3813                 /* Save ratings from notify string */
3814                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3815                 player1Rating = string_to_rating(star_match[1]);
3816                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3817                 player2Rating = string_to_rating(star_match[3]);
3818
3819                 if (appData.debugMode)
3820                   fprintf(debugFP,
3821                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3822                           player1Name, player1Rating,
3823                           player2Name, player2Rating);
3824
3825                 continue;
3826             }
3827
3828             /* Deal with automatic examine mode after a game,
3829                and with IcsObserving -> IcsExamining transition */
3830             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3831                 looking_at(buf, &i, "has made you an examiner of game *")) {
3832
3833                 int gamenum = atoi(star_match[0]);
3834                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3835                     gamenum == ics_gamenum) {
3836                     /* We were already playing or observing this game;
3837                        no need to refetch history */
3838                     gameMode = IcsExamining;
3839                     if (pausing) {
3840                         pauseExamForwardMostMove = forwardMostMove;
3841                     } else if (currentMove < forwardMostMove) {
3842                         ForwardInner(forwardMostMove);
3843                     }
3844                 } else {
3845                     /* I don't think this case really can happen */
3846                     SendToICS(ics_prefix);
3847                     SendToICS("refresh\n");
3848                 }
3849                 continue;
3850             }
3851
3852             /* Error messages */
3853 //          if (ics_user_moved) {
3854             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3855                 if (looking_at(buf, &i, "Illegal move") ||
3856                     looking_at(buf, &i, "Not a legal move") ||
3857                     looking_at(buf, &i, "Your king is in check") ||
3858                     looking_at(buf, &i, "It isn't your turn") ||
3859                     looking_at(buf, &i, "It is not your move")) {
3860                     /* Illegal move */
3861                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3862                         currentMove = forwardMostMove-1;
3863                         DisplayMove(currentMove - 1); /* before DMError */
3864                         DrawPosition(FALSE, boards[currentMove]);
3865                         SwitchClocks(forwardMostMove-1); // [HGM] race
3866                         DisplayBothClocks();
3867                     }
3868                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3869                     ics_user_moved = 0;
3870                     continue;
3871                 }
3872             }
3873
3874             if (looking_at(buf, &i, "still have time") ||
3875                 looking_at(buf, &i, "not out of time") ||
3876                 looking_at(buf, &i, "either player is out of time") ||
3877                 looking_at(buf, &i, "has timeseal; checking")) {
3878                 /* We must have called his flag a little too soon */
3879                 whiteFlag = blackFlag = FALSE;
3880                 continue;
3881             }
3882
3883             if (looking_at(buf, &i, "added * seconds to") ||
3884                 looking_at(buf, &i, "seconds were added to")) {
3885                 /* Update the clocks */
3886                 SendToICS(ics_prefix);
3887                 SendToICS("refresh\n");
3888                 continue;
3889             }
3890
3891             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3892                 ics_clock_paused = TRUE;
3893                 StopClocks();
3894                 continue;
3895             }
3896
3897             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3898                 ics_clock_paused = FALSE;
3899                 StartClocks();
3900                 continue;
3901             }
3902
3903             /* Grab player ratings from the Creating: message.
3904                Note we have to check for the special case when
3905                the ICS inserts things like [white] or [black]. */
3906             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3907                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3908                 /* star_matches:
3909                    0    player 1 name (not necessarily white)
3910                    1    player 1 rating
3911                    2    empty, white, or black (IGNORED)
3912                    3    player 2 name (not necessarily black)
3913                    4    player 2 rating
3914
3915                    The names/ratings are sorted out when the game
3916                    actually starts (below).
3917                 */
3918                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3919                 player1Rating = string_to_rating(star_match[1]);
3920                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3921                 player2Rating = string_to_rating(star_match[4]);
3922
3923                 if (appData.debugMode)
3924                   fprintf(debugFP,
3925                           "Ratings from 'Creating:' %s %d, %s %d\n",
3926                           player1Name, player1Rating,
3927                           player2Name, player2Rating);
3928
3929                 continue;
3930             }
3931
3932             /* Improved generic start/end-of-game messages */
3933             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3934                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3935                 /* If tkind == 0: */
3936                 /* star_match[0] is the game number */
3937                 /*           [1] is the white player's name */
3938                 /*           [2] is the black player's name */
3939                 /* For end-of-game: */
3940                 /*           [3] is the reason for the game end */
3941                 /*           [4] is a PGN end game-token, preceded by " " */
3942                 /* For start-of-game: */
3943                 /*           [3] begins with "Creating" or "Continuing" */
3944                 /*           [4] is " *" or empty (don't care). */
3945                 int gamenum = atoi(star_match[0]);
3946                 char *whitename, *blackname, *why, *endtoken;
3947                 ChessMove endtype = EndOfFile;
3948
3949                 if (tkind == 0) {
3950                   whitename = star_match[1];
3951                   blackname = star_match[2];
3952                   why = star_match[3];
3953                   endtoken = star_match[4];
3954                 } else {
3955                   whitename = star_match[1];
3956                   blackname = star_match[3];
3957                   why = star_match[5];
3958                   endtoken = star_match[6];
3959                 }
3960
3961                 /* Game start messages */
3962                 if (strncmp(why, "Creating ", 9) == 0 ||
3963                     strncmp(why, "Continuing ", 11) == 0) {
3964                     gs_gamenum = gamenum;
3965                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3966                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3967                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3968 #if ZIPPY
3969                     if (appData.zippyPlay) {
3970                         ZippyGameStart(whitename, blackname);
3971                     }
3972 #endif /*ZIPPY*/
3973                     partnerBoardValid = FALSE; // [HGM] bughouse
3974                     continue;
3975                 }
3976
3977                 /* Game end messages */
3978                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3979                     ics_gamenum != gamenum) {
3980                     continue;
3981                 }
3982                 while (endtoken[0] == ' ') endtoken++;
3983                 switch (endtoken[0]) {
3984                   case '*':
3985                   default:
3986                     endtype = GameUnfinished;
3987                     break;
3988                   case '0':
3989                     endtype = BlackWins;
3990                     break;
3991                   case '1':
3992                     if (endtoken[1] == '/')
3993                       endtype = GameIsDrawn;
3994                     else
3995                       endtype = WhiteWins;
3996                     break;
3997                 }
3998                 GameEnds(endtype, why, GE_ICS);
3999 #if ZIPPY
4000                 if (appData.zippyPlay && first.initDone) {
4001                     ZippyGameEnd(endtype, why);
4002                     if (first.pr == NoProc) {
4003                       /* Start the next process early so that we'll
4004                          be ready for the next challenge */
4005                       StartChessProgram(&first);
4006                     }
4007                     /* Send "new" early, in case this command takes
4008                        a long time to finish, so that we'll be ready
4009                        for the next challenge. */
4010                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4011                     Reset(TRUE, TRUE);
4012                 }
4013 #endif /*ZIPPY*/
4014                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4015                 continue;
4016             }
4017
4018             if (looking_at(buf, &i, "Removing game * from observation") ||
4019                 looking_at(buf, &i, "no longer observing game *") ||
4020                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4021                 if (gameMode == IcsObserving &&
4022                     atoi(star_match[0]) == ics_gamenum)
4023                   {
4024                       /* icsEngineAnalyze */
4025                       if (appData.icsEngineAnalyze) {
4026                             ExitAnalyzeMode();
4027                             ModeHighlight();
4028                       }
4029                       StopClocks();
4030                       gameMode = IcsIdle;
4031                       ics_gamenum = -1;
4032                       ics_user_moved = FALSE;
4033                   }
4034                 continue;
4035             }
4036
4037             if (looking_at(buf, &i, "no longer examining game *")) {
4038                 if (gameMode == IcsExamining &&
4039                     atoi(star_match[0]) == ics_gamenum)
4040                   {
4041                       gameMode = IcsIdle;
4042                       ics_gamenum = -1;
4043                       ics_user_moved = FALSE;
4044                   }
4045                 continue;
4046             }
4047
4048             /* Advance leftover_start past any newlines we find,
4049                so only partial lines can get reparsed */
4050             if (looking_at(buf, &i, "\n")) {
4051                 prevColor = curColor;
4052                 if (curColor != ColorNormal) {
4053                     if (oldi > next_out) {
4054                         SendToPlayer(&buf[next_out], oldi - next_out);
4055                         next_out = oldi;
4056                     }
4057                     Colorize(ColorNormal, FALSE);
4058                     curColor = ColorNormal;
4059                 }
4060                 if (started == STARTED_BOARD) {
4061                     started = STARTED_NONE;
4062                     parse[parse_pos] = NULLCHAR;
4063                     ParseBoard12(parse);
4064                     ics_user_moved = 0;
4065
4066                     /* Send premove here */
4067                     if (appData.premove) {
4068                       char str[MSG_SIZ];
4069                       if (currentMove == 0 &&
4070                           gameMode == IcsPlayingWhite &&
4071                           appData.premoveWhite) {
4072                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4073                         if (appData.debugMode)
4074                           fprintf(debugFP, "Sending premove:\n");
4075                         SendToICS(str);
4076                       } else if (currentMove == 1 &&
4077                                  gameMode == IcsPlayingBlack &&
4078                                  appData.premoveBlack) {
4079                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4080                         if (appData.debugMode)
4081                           fprintf(debugFP, "Sending premove:\n");
4082                         SendToICS(str);
4083                       } else if (gotPremove) {
4084                         gotPremove = 0;
4085                         ClearPremoveHighlights();
4086                         if (appData.debugMode)
4087                           fprintf(debugFP, "Sending premove:\n");
4088                           UserMoveEvent(premoveFromX, premoveFromY,
4089                                         premoveToX, premoveToY,
4090                                         premovePromoChar);
4091                       }
4092                     }
4093
4094                     /* Usually suppress following prompt */
4095                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4096                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4097                         if (looking_at(buf, &i, "*% ")) {
4098                             savingComment = FALSE;
4099                             suppressKibitz = 0;
4100                         }
4101                     }
4102                     next_out = i;
4103                 } else if (started == STARTED_HOLDINGS) {
4104                     int gamenum;
4105                     char new_piece[MSG_SIZ];
4106                     started = STARTED_NONE;
4107                     parse[parse_pos] = NULLCHAR;
4108                     if (appData.debugMode)
4109                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4110                                                         parse, currentMove);
4111                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4112                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4113                         if (gameInfo.variant == VariantNormal) {
4114                           /* [HGM] We seem to switch variant during a game!
4115                            * Presumably no holdings were displayed, so we have
4116                            * to move the position two files to the right to
4117                            * create room for them!
4118                            */
4119                           VariantClass newVariant;
4120                           switch(gameInfo.boardWidth) { // base guess on board width
4121                                 case 9:  newVariant = VariantShogi; break;
4122                                 case 10: newVariant = VariantGreat; break;
4123                                 default: newVariant = VariantCrazyhouse; break;
4124                           }
4125                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4126                           /* Get a move list just to see the header, which
4127                              will tell us whether this is really bug or zh */
4128                           if (ics_getting_history == H_FALSE) {
4129                             ics_getting_history = H_REQUESTED;
4130                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4131                             SendToICS(str);
4132                           }
4133                         }
4134                         new_piece[0] = NULLCHAR;
4135                         sscanf(parse, "game %d white [%s black [%s <- %s",
4136                                &gamenum, white_holding, black_holding,
4137                                new_piece);
4138                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4139                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4140                         /* [HGM] copy holdings to board holdings area */
4141                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4142                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4143                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4144 #if ZIPPY
4145                         if (appData.zippyPlay && first.initDone) {
4146                             ZippyHoldings(white_holding, black_holding,
4147                                           new_piece);
4148                         }
4149 #endif /*ZIPPY*/
4150                         if (tinyLayout || smallLayout) {
4151                             char wh[16], bh[16];
4152                             PackHolding(wh, white_holding);
4153                             PackHolding(bh, black_holding);
4154                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4155                                     gameInfo.white, gameInfo.black);
4156                         } else {
4157                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4158                                     gameInfo.white, white_holding, _("vs."),
4159                                     gameInfo.black, black_holding);
4160                         }
4161                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4162                         DrawPosition(FALSE, boards[currentMove]);
4163                         DisplayTitle(str);
4164                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4165                         sscanf(parse, "game %d white [%s black [%s <- %s",
4166                                &gamenum, white_holding, black_holding,
4167                                new_piece);
4168                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4169                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4170                         /* [HGM] copy holdings to partner-board holdings area */
4171                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4172                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4173                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4174                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4176                       }
4177                     }
4178                     /* Suppress following prompt */
4179                     if (looking_at(buf, &i, "*% ")) {
4180                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4181                         savingComment = FALSE;
4182                         suppressKibitz = 0;
4183                     }
4184                     next_out = i;
4185                 }
4186                 continue;
4187             }
4188
4189             i++;                /* skip unparsed character and loop back */
4190         }
4191
4192         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4193 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4194 //          SendToPlayer(&buf[next_out], i - next_out);
4195             started != STARTED_HOLDINGS && leftover_start > next_out) {
4196             SendToPlayer(&buf[next_out], leftover_start - next_out);
4197             next_out = i;
4198         }
4199
4200         leftover_len = buf_len - leftover_start;
4201         /* if buffer ends with something we couldn't parse,
4202            reparse it after appending the next read */
4203
4204     } else if (count == 0) {
4205         RemoveInputSource(isr);
4206         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4207     } else {
4208         DisplayFatalError(_("Error reading from ICS"), error, 1);
4209     }
4210 }
4211
4212
4213 /* Board style 12 looks like this:
4214
4215    <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
4216
4217  * The "<12> " is stripped before it gets to this routine.  The two
4218  * trailing 0's (flip state and clock ticking) are later addition, and
4219  * some chess servers may not have them, or may have only the first.
4220  * Additional trailing fields may be added in the future.
4221  */
4222
4223 #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"
4224
4225 #define RELATION_OBSERVING_PLAYED    0
4226 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4227 #define RELATION_PLAYING_MYMOVE      1
4228 #define RELATION_PLAYING_NOTMYMOVE  -1
4229 #define RELATION_EXAMINING           2
4230 #define RELATION_ISOLATED_BOARD     -3
4231 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4232
4233 void
4234 ParseBoard12 (char *string)
4235 {
4236 #if ZIPPY
4237     int i, takeback;
4238     char *bookHit = NULL; // [HGM] book
4239 #endif
4240     GameMode newGameMode;
4241     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4242     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4243     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4244     char to_play, board_chars[200];
4245     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4246     char black[32], white[32];
4247     Board board;
4248     int prevMove = currentMove;
4249     int ticking = 2;
4250     ChessMove moveType;
4251     int fromX, fromY, toX, toY;
4252     char promoChar;
4253     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4254     Boolean weird = FALSE, reqFlag = FALSE;
4255
4256     fromX = fromY = toX = toY = -1;
4257
4258     newGame = FALSE;
4259
4260     if (appData.debugMode)
4261       fprintf(debugFP, "Parsing board: %s\n", string);
4262
4263     move_str[0] = NULLCHAR;
4264     elapsed_time[0] = NULLCHAR;
4265     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4266         int  i = 0, j;
4267         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4268             if(string[i] == ' ') { ranks++; files = 0; }
4269             else files++;
4270             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4271             i++;
4272         }
4273         for(j = 0; j <i; j++) board_chars[j] = string[j];
4274         board_chars[i] = '\0';
4275         string += i + 1;
4276     }
4277     n = sscanf(string, PATTERN, &to_play, &double_push,
4278                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4279                &gamenum, white, black, &relation, &basetime, &increment,
4280                &white_stren, &black_stren, &white_time, &black_time,
4281                &moveNum, str, elapsed_time, move_str, &ics_flip,
4282                &ticking);
4283
4284     if (n < 21) {
4285         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4286         DisplayError(str, 0);
4287         return;
4288     }
4289
4290     /* Convert the move number to internal form */
4291     moveNum = (moveNum - 1) * 2;
4292     if (to_play == 'B') moveNum++;
4293     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4294       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4295                         0, 1);
4296       return;
4297     }
4298
4299     switch (relation) {
4300       case RELATION_OBSERVING_PLAYED:
4301       case RELATION_OBSERVING_STATIC:
4302         if (gamenum == -1) {
4303             /* Old ICC buglet */
4304             relation = RELATION_OBSERVING_STATIC;
4305         }
4306         newGameMode = IcsObserving;
4307         break;
4308       case RELATION_PLAYING_MYMOVE:
4309       case RELATION_PLAYING_NOTMYMOVE:
4310         newGameMode =
4311           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4312             IcsPlayingWhite : IcsPlayingBlack;
4313         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4314         break;
4315       case RELATION_EXAMINING:
4316         newGameMode = IcsExamining;
4317         break;
4318       case RELATION_ISOLATED_BOARD:
4319       default:
4320         /* Just display this board.  If user was doing something else,
4321            we will forget about it until the next board comes. */
4322         newGameMode = IcsIdle;
4323         break;
4324       case RELATION_STARTING_POSITION:
4325         newGameMode = gameMode;
4326         break;
4327     }
4328
4329     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4330         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4331          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4332       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4333       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4334       static int lastBgGame = -1;
4335       char *toSqr;
4336       for (k = 0; k < ranks; k++) {
4337         for (j = 0; j < files; j++)
4338           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4339         if(gameInfo.holdingsWidth > 1) {
4340              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4341              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4342         }
4343       }
4344       CopyBoard(partnerBoard, board);
4345       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4346         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4347         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4348       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4349       if(toSqr = strchr(str, '-')) {
4350         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4351         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4352       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4353       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4354       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4355       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4356       if(twoBoards) {
4357           DisplayWhiteClock(white_time*fac, to_play == 'W');
4358           DisplayBlackClock(black_time*fac, to_play != 'W');
4359           activePartner = to_play;
4360           if(gamenum != lastBgGame) {
4361               char buf[MSG_SIZ];
4362               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4363               DisplayTitle(buf);
4364           }
4365           lastBgGame = gamenum;
4366           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4367                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4368       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4369                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4370       if(!twoBoards) DisplayMessage(partnerStatus, "");
4371         partnerBoardValid = TRUE;
4372       return;
4373     }
4374
4375     if(appData.dualBoard && appData.bgObserve) {
4376         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4377             SendToICS(ics_prefix), SendToICS("pobserve\n");
4378         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4379             char buf[MSG_SIZ];
4380             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4381             SendToICS(buf);
4382         }
4383     }
4384
4385     /* Modify behavior for initial board display on move listing
4386        of wild games.
4387        */
4388     switch (ics_getting_history) {
4389       case H_FALSE:
4390       case H_REQUESTED:
4391         break;
4392       case H_GOT_REQ_HEADER:
4393       case H_GOT_UNREQ_HEADER:
4394         /* This is the initial position of the current game */
4395         gamenum = ics_gamenum;
4396         moveNum = 0;            /* old ICS bug workaround */
4397         if (to_play == 'B') {
4398           startedFromSetupPosition = TRUE;
4399           blackPlaysFirst = TRUE;
4400           moveNum = 1;
4401           if (forwardMostMove == 0) forwardMostMove = 1;
4402           if (backwardMostMove == 0) backwardMostMove = 1;
4403           if (currentMove == 0) currentMove = 1;
4404         }
4405         newGameMode = gameMode;
4406         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4407         break;
4408       case H_GOT_UNWANTED_HEADER:
4409         /* This is an initial board that we don't want */
4410         return;
4411       case H_GETTING_MOVES:
4412         /* Should not happen */
4413         DisplayError(_("Error gathering move list: extra board"), 0);
4414         ics_getting_history = H_FALSE;
4415         return;
4416     }
4417
4418    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4419                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4420                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4421      /* [HGM] We seem to have switched variant unexpectedly
4422       * Try to guess new variant from board size
4423       */
4424           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4425           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4426           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4427           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4428           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4429           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4430           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4431           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4432           /* Get a move list just to see the header, which
4433              will tell us whether this is really bug or zh */
4434           if (ics_getting_history == H_FALSE) {
4435             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4436             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4437             SendToICS(str);
4438           }
4439     }
4440
4441     /* Take action if this is the first board of a new game, or of a
4442        different game than is currently being displayed.  */
4443     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4444         relation == RELATION_ISOLATED_BOARD) {
4445
4446         /* Forget the old game and get the history (if any) of the new one */
4447         if (gameMode != BeginningOfGame) {
4448           Reset(TRUE, TRUE);
4449         }
4450         newGame = TRUE;
4451         if (appData.autoRaiseBoard) BoardToTop();
4452         prevMove = -3;
4453         if (gamenum == -1) {
4454             newGameMode = IcsIdle;
4455         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4456                    appData.getMoveList && !reqFlag) {
4457             /* Need to get game history */
4458             ics_getting_history = H_REQUESTED;
4459             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4460             SendToICS(str);
4461         }
4462
4463         /* Initially flip the board to have black on the bottom if playing
4464            black or if the ICS flip flag is set, but let the user change
4465            it with the Flip View button. */
4466         flipView = appData.autoFlipView ?
4467           (newGameMode == IcsPlayingBlack) || ics_flip :
4468           appData.flipView;
4469
4470         /* Done with values from previous mode; copy in new ones */
4471         gameMode = newGameMode;
4472         ModeHighlight();
4473         ics_gamenum = gamenum;
4474         if (gamenum == gs_gamenum) {
4475             int klen = strlen(gs_kind);
4476             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4477             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4478             gameInfo.event = StrSave(str);
4479         } else {
4480             gameInfo.event = StrSave("ICS game");
4481         }
4482         gameInfo.site = StrSave(appData.icsHost);
4483         gameInfo.date = PGNDate();
4484         gameInfo.round = StrSave("-");
4485         gameInfo.white = StrSave(white);
4486         gameInfo.black = StrSave(black);
4487         timeControl = basetime * 60 * 1000;
4488         timeControl_2 = 0;
4489         timeIncrement = increment * 1000;
4490         movesPerSession = 0;
4491         gameInfo.timeControl = TimeControlTagValue();
4492         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4493   if (appData.debugMode) {
4494     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4495     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4496     setbuf(debugFP, NULL);
4497   }
4498
4499         gameInfo.outOfBook = NULL;
4500
4501         /* Do we have the ratings? */
4502         if (strcmp(player1Name, white) == 0 &&
4503             strcmp(player2Name, black) == 0) {
4504             if (appData.debugMode)
4505               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4506                       player1Rating, player2Rating);
4507             gameInfo.whiteRating = player1Rating;
4508             gameInfo.blackRating = player2Rating;
4509         } else if (strcmp(player2Name, white) == 0 &&
4510                    strcmp(player1Name, black) == 0) {
4511             if (appData.debugMode)
4512               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4513                       player2Rating, player1Rating);
4514             gameInfo.whiteRating = player2Rating;
4515             gameInfo.blackRating = player1Rating;
4516         }
4517         player1Name[0] = player2Name[0] = NULLCHAR;
4518
4519         /* Silence shouts if requested */
4520         if (appData.quietPlay &&
4521             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4522             SendToICS(ics_prefix);
4523             SendToICS("set shout 0\n");
4524         }
4525     }
4526
4527     /* Deal with midgame name changes */
4528     if (!newGame) {
4529         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4530             if (gameInfo.white) free(gameInfo.white);
4531             gameInfo.white = StrSave(white);
4532         }
4533         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4534             if (gameInfo.black) free(gameInfo.black);
4535             gameInfo.black = StrSave(black);
4536         }
4537     }
4538
4539     /* Throw away game result if anything actually changes in examine mode */
4540     if (gameMode == IcsExamining && !newGame) {
4541         gameInfo.result = GameUnfinished;
4542         if (gameInfo.resultDetails != NULL) {
4543             free(gameInfo.resultDetails);
4544             gameInfo.resultDetails = NULL;
4545         }
4546     }
4547
4548     /* In pausing && IcsExamining mode, we ignore boards coming
4549        in if they are in a different variation than we are. */
4550     if (pauseExamInvalid) return;
4551     if (pausing && gameMode == IcsExamining) {
4552         if (moveNum <= pauseExamForwardMostMove) {
4553             pauseExamInvalid = TRUE;
4554             forwardMostMove = pauseExamForwardMostMove;
4555             return;
4556         }
4557     }
4558
4559   if (appData.debugMode) {
4560     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4561   }
4562     /* Parse the board */
4563     for (k = 0; k < ranks; k++) {
4564       for (j = 0; j < files; j++)
4565         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4566       if(gameInfo.holdingsWidth > 1) {
4567            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4568            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4569       }
4570     }
4571     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4572       board[5][BOARD_RGHT+1] = WhiteAngel;
4573       board[6][BOARD_RGHT+1] = WhiteMarshall;
4574       board[1][0] = BlackMarshall;
4575       board[2][0] = BlackAngel;
4576       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4577     }
4578     CopyBoard(boards[moveNum], board);
4579     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4580     if (moveNum == 0) {
4581         startedFromSetupPosition =
4582           !CompareBoards(board, initialPosition);
4583         if(startedFromSetupPosition)
4584             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4585     }
4586
4587     /* [HGM] Set castling rights. Take the outermost Rooks,
4588        to make it also work for FRC opening positions. Note that board12
4589        is really defective for later FRC positions, as it has no way to
4590        indicate which Rook can castle if they are on the same side of King.
4591        For the initial position we grant rights to the outermost Rooks,
4592        and remember thos rights, and we then copy them on positions
4593        later in an FRC game. This means WB might not recognize castlings with
4594        Rooks that have moved back to their original position as illegal,
4595        but in ICS mode that is not its job anyway.
4596     */
4597     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4598     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4599
4600         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4601             if(board[0][i] == WhiteRook) j = i;
4602         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4603         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4604             if(board[0][i] == WhiteRook) j = i;
4605         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4606         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4607             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4608         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4609         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4610             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4611         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4612
4613         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4614         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4615         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4616             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4617         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4618             if(board[BOARD_HEIGHT-1][k] == bKing)
4619                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4620         if(gameInfo.variant == VariantTwoKings) {
4621             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4622             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4623             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4624         }
4625     } else { int r;
4626         r = boards[moveNum][CASTLING][0] = initialRights[0];
4627         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4628         r = boards[moveNum][CASTLING][1] = initialRights[1];
4629         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4630         r = boards[moveNum][CASTLING][3] = initialRights[3];
4631         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4632         r = boards[moveNum][CASTLING][4] = initialRights[4];
4633         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4634         /* wildcastle kludge: always assume King has rights */
4635         r = boards[moveNum][CASTLING][2] = initialRights[2];
4636         r = boards[moveNum][CASTLING][5] = initialRights[5];
4637     }
4638     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4639     boards[moveNum][EP_STATUS] = EP_NONE;
4640     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4641     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4642     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4643
4644
4645     if (ics_getting_history == H_GOT_REQ_HEADER ||
4646         ics_getting_history == H_GOT_UNREQ_HEADER) {
4647         /* This was an initial position from a move list, not
4648            the current position */
4649         return;
4650     }
4651
4652     /* Update currentMove and known move number limits */
4653     newMove = newGame || moveNum > forwardMostMove;
4654
4655     if (newGame) {
4656         forwardMostMove = backwardMostMove = currentMove = moveNum;
4657         if (gameMode == IcsExamining && moveNum == 0) {
4658           /* Workaround for ICS limitation: we are not told the wild
4659              type when starting to examine a game.  But if we ask for
4660              the move list, the move list header will tell us */
4661             ics_getting_history = H_REQUESTED;
4662             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4663             SendToICS(str);
4664         }
4665     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4666                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4667 #if ZIPPY
4668         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4669         /* [HGM] applied this also to an engine that is silently watching        */
4670         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4671             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4672             gameInfo.variant == currentlyInitializedVariant) {
4673           takeback = forwardMostMove - moveNum;
4674           for (i = 0; i < takeback; i++) {
4675             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4676             SendToProgram("undo\n", &first);
4677           }
4678         }
4679 #endif
4680
4681         forwardMostMove = moveNum;
4682         if (!pausing || currentMove > forwardMostMove)
4683           currentMove = forwardMostMove;
4684     } else {
4685         /* New part of history that is not contiguous with old part */
4686         if (pausing && gameMode == IcsExamining) {
4687             pauseExamInvalid = TRUE;
4688             forwardMostMove = pauseExamForwardMostMove;
4689             return;
4690         }
4691         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4692 #if ZIPPY
4693             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4694                 // [HGM] when we will receive the move list we now request, it will be
4695                 // fed to the engine from the first move on. So if the engine is not
4696                 // in the initial position now, bring it there.
4697                 InitChessProgram(&first, 0);
4698             }
4699 #endif
4700             ics_getting_history = H_REQUESTED;
4701             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4702             SendToICS(str);
4703         }
4704         forwardMostMove = backwardMostMove = currentMove = moveNum;
4705     }
4706
4707     /* Update the clocks */
4708     if (strchr(elapsed_time, '.')) {
4709       /* Time is in ms */
4710       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4711       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4712     } else {
4713       /* Time is in seconds */
4714       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4715       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4716     }
4717
4718
4719 #if ZIPPY
4720     if (appData.zippyPlay && newGame &&
4721         gameMode != IcsObserving && gameMode != IcsIdle &&
4722         gameMode != IcsExamining)
4723       ZippyFirstBoard(moveNum, basetime, increment);
4724 #endif
4725
4726     /* Put the move on the move list, first converting
4727        to canonical algebraic form. */
4728     if (moveNum > 0) {
4729   if (appData.debugMode) {
4730     int f = forwardMostMove;
4731     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4732             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4733             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4734     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4735     fprintf(debugFP, "moveNum = %d\n", moveNum);
4736     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4737     setbuf(debugFP, NULL);
4738   }
4739         if (moveNum <= backwardMostMove) {
4740             /* We don't know what the board looked like before
4741                this move.  Punt. */
4742           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743             strcat(parseList[moveNum - 1], " ");
4744             strcat(parseList[moveNum - 1], elapsed_time);
4745             moveList[moveNum - 1][0] = NULLCHAR;
4746         } else if (strcmp(move_str, "none") == 0) {
4747             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4748             /* Again, we don't know what the board looked like;
4749                this is really the start of the game. */
4750             parseList[moveNum - 1][0] = NULLCHAR;
4751             moveList[moveNum - 1][0] = NULLCHAR;
4752             backwardMostMove = moveNum;
4753             startedFromSetupPosition = TRUE;
4754             fromX = fromY = toX = toY = -1;
4755         } else {
4756           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4757           //                 So we parse the long-algebraic move string in stead of the SAN move
4758           int valid; char buf[MSG_SIZ], *prom;
4759
4760           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4761                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4762           // str looks something like "Q/a1-a2"; kill the slash
4763           if(str[1] == '/')
4764             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4765           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4766           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4767                 strcat(buf, prom); // long move lacks promo specification!
4768           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4769                 if(appData.debugMode)
4770                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4771                 safeStrCpy(move_str, buf, MSG_SIZ);
4772           }
4773           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4774                                 &fromX, &fromY, &toX, &toY, &promoChar)
4775                || ParseOneMove(buf, moveNum - 1, &moveType,
4776                                 &fromX, &fromY, &toX, &toY, &promoChar);
4777           // end of long SAN patch
4778           if (valid) {
4779             (void) CoordsToAlgebraic(boards[moveNum - 1],
4780                                      PosFlags(moveNum - 1),
4781                                      fromY, fromX, toY, toX, promoChar,
4782                                      parseList[moveNum-1]);
4783             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4784               case MT_NONE:
4785               case MT_STALEMATE:
4786               default:
4787                 break;
4788               case MT_CHECK:
4789                 if(gameInfo.variant != VariantShogi)
4790                     strcat(parseList[moveNum - 1], "+");
4791                 break;
4792               case MT_CHECKMATE:
4793               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4794                 strcat(parseList[moveNum - 1], "#");
4795                 break;
4796             }
4797             strcat(parseList[moveNum - 1], " ");
4798             strcat(parseList[moveNum - 1], elapsed_time);
4799             /* currentMoveString is set as a side-effect of ParseOneMove */
4800             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4801             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4802             strcat(moveList[moveNum - 1], "\n");
4803
4804             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4805                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4806               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4807                 ChessSquare old, new = boards[moveNum][k][j];
4808                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4809                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4810                   if(old == new) continue;
4811                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4812                   else if(new == WhiteWazir || new == BlackWazir) {
4813                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4814                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4815                       else boards[moveNum][k][j] = old; // preserve type of Gold
4816                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4817                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4818               }
4819           } else {
4820             /* Move from ICS was illegal!?  Punt. */
4821             if (appData.debugMode) {
4822               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4823               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4824             }
4825             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4826             strcat(parseList[moveNum - 1], " ");
4827             strcat(parseList[moveNum - 1], elapsed_time);
4828             moveList[moveNum - 1][0] = NULLCHAR;
4829             fromX = fromY = toX = toY = -1;
4830           }
4831         }
4832   if (appData.debugMode) {
4833     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4834     setbuf(debugFP, NULL);
4835   }
4836
4837 #if ZIPPY
4838         /* Send move to chess program (BEFORE animating it). */
4839         if (appData.zippyPlay && !newGame && newMove &&
4840            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4841
4842             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4843                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4844                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4845                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4846                             move_str);
4847                     DisplayError(str, 0);
4848                 } else {
4849                     if (first.sendTime) {
4850                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4851                     }
4852                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4853                     if (firstMove && !bookHit) {
4854                         firstMove = FALSE;
4855                         if (first.useColors) {
4856                           SendToProgram(gameMode == IcsPlayingWhite ?
4857                                         "white\ngo\n" :
4858                                         "black\ngo\n", &first);
4859                         } else {
4860                           SendToProgram("go\n", &first);
4861                         }
4862                         first.maybeThinking = TRUE;
4863                     }
4864                 }
4865             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4866               if (moveList[moveNum - 1][0] == NULLCHAR) {
4867                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4868                 DisplayError(str, 0);
4869               } else {
4870                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4871                 SendMoveToProgram(moveNum - 1, &first);
4872               }
4873             }
4874         }
4875 #endif
4876     }
4877
4878     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4879         /* If move comes from a remote source, animate it.  If it
4880            isn't remote, it will have already been animated. */
4881         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4882             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4883         }
4884         if (!pausing && appData.highlightLastMove) {
4885             SetHighlights(fromX, fromY, toX, toY);
4886         }
4887     }
4888
4889     /* Start the clocks */
4890     whiteFlag = blackFlag = FALSE;
4891     appData.clockMode = !(basetime == 0 && increment == 0);
4892     if (ticking == 0) {
4893       ics_clock_paused = TRUE;
4894       StopClocks();
4895     } else if (ticking == 1) {
4896       ics_clock_paused = FALSE;
4897     }
4898     if (gameMode == IcsIdle ||
4899         relation == RELATION_OBSERVING_STATIC ||
4900         relation == RELATION_EXAMINING ||
4901         ics_clock_paused)
4902       DisplayBothClocks();
4903     else
4904       StartClocks();
4905
4906     /* Display opponents and material strengths */
4907     if (gameInfo.variant != VariantBughouse &&
4908         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4909         if (tinyLayout || smallLayout) {
4910             if(gameInfo.variant == VariantNormal)
4911               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4912                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4913                     basetime, increment);
4914             else
4915               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4916                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4917                     basetime, increment, (int) gameInfo.variant);
4918         } else {
4919             if(gameInfo.variant == VariantNormal)
4920               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4921                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4922                     basetime, increment);
4923             else
4924               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4925                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4926                     basetime, increment, VariantName(gameInfo.variant));
4927         }
4928         DisplayTitle(str);
4929   if (appData.debugMode) {
4930     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4931   }
4932     }
4933
4934
4935     /* Display the board */
4936     if (!pausing && !appData.noGUI) {
4937
4938       if (appData.premove)
4939           if (!gotPremove ||
4940              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4941              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4942               ClearPremoveHighlights();
4943
4944       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4945         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4946       DrawPosition(j, boards[currentMove]);
4947
4948       DisplayMove(moveNum - 1);
4949       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4950             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4951               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4952         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4953       }
4954     }
4955
4956     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4957 #if ZIPPY
4958     if(bookHit) { // [HGM] book: simulate book reply
4959         static char bookMove[MSG_SIZ]; // a bit generous?
4960
4961         programStats.nodes = programStats.depth = programStats.time =
4962         programStats.score = programStats.got_only_move = 0;
4963         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4964
4965         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4966         strcat(bookMove, bookHit);
4967         HandleMachineMove(bookMove, &first);
4968     }
4969 #endif
4970 }
4971
4972 void
4973 GetMoveListEvent ()
4974 {
4975     char buf[MSG_SIZ];
4976     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4977         ics_getting_history = H_REQUESTED;
4978         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4979         SendToICS(buf);
4980     }
4981 }
4982
4983 void
4984 SendToBoth (char *msg)
4985 {   // to make it easy to keep two engines in step in dual analysis
4986     SendToProgram(msg, &first);
4987     if(second.analyzing) SendToProgram(msg, &second);
4988 }
4989
4990 void
4991 AnalysisPeriodicEvent (int force)
4992 {
4993     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4994          && !force) || !appData.periodicUpdates)
4995       return;
4996
4997     /* Send . command to Crafty to collect stats */
4998     SendToBoth(".\n");
4999
5000     /* Don't send another until we get a response (this makes
5001        us stop sending to old Crafty's which don't understand
5002        the "." command (sending illegal cmds resets node count & time,
5003        which looks bad)) */
5004     programStats.ok_to_send = 0;
5005 }
5006
5007 void
5008 ics_update_width (int new_width)
5009 {
5010         ics_printf("set width %d\n", new_width);
5011 }
5012
5013 void
5014 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5015 {
5016     char buf[MSG_SIZ];
5017
5018     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5019         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChu) {
5020             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5021             SendToProgram(buf, cps);
5022             return;
5023         }
5024         // null move in variant where engine does not understand it (for analysis purposes)
5025         SendBoard(cps, moveNum + 1); // send position after move in stead.
5026         return;
5027     }
5028     if (cps->useUsermove) {
5029       SendToProgram("usermove ", cps);
5030     }
5031     if (cps->useSAN) {
5032       char *space;
5033       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5034         int len = space - parseList[moveNum];
5035         memcpy(buf, parseList[moveNum], len);
5036         buf[len++] = '\n';
5037         buf[len] = NULLCHAR;
5038       } else {
5039         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5040       }
5041       SendToProgram(buf, cps);
5042     } else {
5043       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5044         AlphaRank(moveList[moveNum], 4);
5045         SendToProgram(moveList[moveNum], cps);
5046         AlphaRank(moveList[moveNum], 4); // and back
5047       } else
5048       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5049        * the engine. It would be nice to have a better way to identify castle
5050        * moves here. */
5051       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5052                                                                          && cps->useOOCastle) {
5053         int fromX = moveList[moveNum][0] - AAA;
5054         int fromY = moveList[moveNum][1] - ONE;
5055         int toX = moveList[moveNum][2] - AAA;
5056         int toY = moveList[moveNum][3] - ONE;
5057         if((boards[moveNum][fromY][fromX] == WhiteKing
5058             && boards[moveNum][toY][toX] == WhiteRook)
5059            || (boards[moveNum][fromY][fromX] == BlackKing
5060                && boards[moveNum][toY][toX] == BlackRook)) {
5061           if(toX > fromX) SendToProgram("O-O\n", cps);
5062           else SendToProgram("O-O-O\n", cps);
5063         }
5064         else SendToProgram(moveList[moveNum], cps);
5065       } else
5066       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5067           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5068                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5069                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5070                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5071           SendToProgram(buf, cps);
5072       } else
5073       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5074         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5075           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5076           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5077                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5078         } else
5079           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5080                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5081         SendToProgram(buf, cps);
5082       }
5083       else SendToProgram(moveList[moveNum], cps);
5084       /* End of additions by Tord */
5085     }
5086
5087     /* [HGM] setting up the opening has brought engine in force mode! */
5088     /*       Send 'go' if we are in a mode where machine should play. */
5089     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5090         (gameMode == TwoMachinesPlay   ||
5091 #if ZIPPY
5092          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5093 #endif
5094          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5095         SendToProgram("go\n", cps);
5096   if (appData.debugMode) {
5097     fprintf(debugFP, "(extra)\n");
5098   }
5099     }
5100     setboardSpoiledMachineBlack = 0;
5101 }
5102
5103 void
5104 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5105 {
5106     char user_move[MSG_SIZ];
5107     char suffix[4];
5108
5109     if(gameInfo.variant == VariantSChess && promoChar) {
5110         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5111         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5112     } else suffix[0] = NULLCHAR;
5113
5114     switch (moveType) {
5115       default:
5116         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5117                 (int)moveType, fromX, fromY, toX, toY);
5118         DisplayError(user_move + strlen("say "), 0);
5119         break;
5120       case WhiteKingSideCastle:
5121       case BlackKingSideCastle:
5122       case WhiteQueenSideCastleWild:
5123       case BlackQueenSideCastleWild:
5124       /* PUSH Fabien */
5125       case WhiteHSideCastleFR:
5126       case BlackHSideCastleFR:
5127       /* POP Fabien */
5128         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5129         break;
5130       case WhiteQueenSideCastle:
5131       case BlackQueenSideCastle:
5132       case WhiteKingSideCastleWild:
5133       case BlackKingSideCastleWild:
5134       /* PUSH Fabien */
5135       case WhiteASideCastleFR:
5136       case BlackASideCastleFR:
5137       /* POP Fabien */
5138         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5139         break;
5140       case WhiteNonPromotion:
5141       case BlackNonPromotion:
5142         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5143         break;
5144       case WhitePromotion:
5145       case BlackPromotion:
5146         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5147            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5148           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5149                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5150                 PieceToChar(WhiteFerz));
5151         else if(gameInfo.variant == VariantGreat)
5152           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5153                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5154                 PieceToChar(WhiteMan));
5155         else
5156           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5157                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5158                 promoChar);
5159         break;
5160       case WhiteDrop:
5161       case BlackDrop:
5162       drop:
5163         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5164                  ToUpper(PieceToChar((ChessSquare) fromX)),
5165                  AAA + toX, ONE + toY);
5166         break;
5167       case IllegalMove:  /* could be a variant we don't quite understand */
5168         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5169       case NormalMove:
5170       case WhiteCapturesEnPassant:
5171       case BlackCapturesEnPassant:
5172         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5173                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5174         break;
5175     }
5176     SendToICS(user_move);
5177     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5178         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5179 }
5180
5181 void
5182 UploadGameEvent ()
5183 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5184     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5185     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5186     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5187       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5188       return;
5189     }
5190     if(gameMode != IcsExamining) { // is this ever not the case?
5191         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5192
5193         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5194           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5195         } else { // on FICS we must first go to general examine mode
5196           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5197         }
5198         if(gameInfo.variant != VariantNormal) {
5199             // try figure out wild number, as xboard names are not always valid on ICS
5200             for(i=1; i<=36; i++) {
5201               snprintf(buf, MSG_SIZ, "wild/%d", i);
5202                 if(StringToVariant(buf) == gameInfo.variant) break;
5203             }
5204             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5205             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5206             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5207         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5208         SendToICS(ics_prefix);
5209         SendToICS(buf);
5210         if(startedFromSetupPosition || backwardMostMove != 0) {
5211           fen = PositionToFEN(backwardMostMove, NULL, 1);
5212           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5213             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5214             SendToICS(buf);
5215           } else { // FICS: everything has to set by separate bsetup commands
5216             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5217             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5218             SendToICS(buf);
5219             if(!WhiteOnMove(backwardMostMove)) {
5220                 SendToICS("bsetup tomove black\n");
5221             }
5222             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5223             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5224             SendToICS(buf);
5225             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5226             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5227             SendToICS(buf);
5228             i = boards[backwardMostMove][EP_STATUS];
5229             if(i >= 0) { // set e.p.
5230               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5231                 SendToICS(buf);
5232             }
5233             bsetup++;
5234           }
5235         }
5236       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5237             SendToICS("bsetup done\n"); // switch to normal examining.
5238     }
5239     for(i = backwardMostMove; i<last; i++) {
5240         char buf[20];
5241         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5242         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5243             int len = strlen(moveList[i]);
5244             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5245             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5246         }
5247         SendToICS(buf);
5248     }
5249     SendToICS(ics_prefix);
5250     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5251 }
5252
5253 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5254
5255 void
5256 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5257 {
5258     if (rf == DROP_RANK) {
5259       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5260       sprintf(move, "%c@%c%c\n",
5261                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5262     } else {
5263         if (promoChar == 'x' || promoChar == NULLCHAR) {
5264           sprintf(move, "%c%c%c%c\n",
5265                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5266           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5267         } else {
5268             sprintf(move, "%c%c%c%c%c\n",
5269                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5270         }
5271     }
5272 }
5273
5274 void
5275 ProcessICSInitScript (FILE *f)
5276 {
5277     char buf[MSG_SIZ];
5278
5279     while (fgets(buf, MSG_SIZ, f)) {
5280         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5281     }
5282
5283     fclose(f);
5284 }
5285
5286
5287 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5288 int dragging;
5289 static ClickType lastClickType;
5290
5291 void
5292 Sweep (int step)
5293 {
5294     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5295     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5296     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5297     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5298     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5299     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5300     do {
5301         promoSweep -= step;
5302         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5303         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5304         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5305         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5306         if(!step) step = -1;
5307     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5308             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5309             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5310     if(toX >= 0) {
5311         int victim = boards[currentMove][toY][toX];
5312         boards[currentMove][toY][toX] = promoSweep;
5313         DrawPosition(FALSE, boards[currentMove]);
5314         boards[currentMove][toY][toX] = victim;
5315     } else
5316     ChangeDragPiece(promoSweep);
5317 }
5318
5319 int
5320 PromoScroll (int x, int y)
5321 {
5322   int step = 0;
5323
5324   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5325   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5326   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5327   if(!step) return FALSE;
5328   lastX = x; lastY = y;
5329   if((promoSweep < BlackPawn) == flipView) step = -step;
5330   if(step > 0) selectFlag = 1;
5331   if(!selectFlag) Sweep(step);
5332   return FALSE;
5333 }
5334
5335 void
5336 NextPiece (int step)
5337 {
5338     ChessSquare piece = boards[currentMove][toY][toX];
5339     do {
5340         pieceSweep -= step;
5341         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5342         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5343         if(!step) step = -1;
5344     } while(PieceToChar(pieceSweep) == '.');
5345     boards[currentMove][toY][toX] = pieceSweep;
5346     DrawPosition(FALSE, boards[currentMove]);
5347     boards[currentMove][toY][toX] = piece;
5348 }
5349 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5350 void
5351 AlphaRank (char *move, int n)
5352 {
5353 //    char *p = move, c; int x, y;
5354
5355     if (appData.debugMode) {
5356         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5357     }
5358
5359     if(move[1]=='*' &&
5360        move[2]>='0' && move[2]<='9' &&
5361        move[3]>='a' && move[3]<='x'    ) {
5362         move[1] = '@';
5363         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5364         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5365     } else
5366     if(move[0]>='0' && move[0]<='9' &&
5367        move[1]>='a' && move[1]<='x' &&
5368        move[2]>='0' && move[2]<='9' &&
5369        move[3]>='a' && move[3]<='x'    ) {
5370         /* input move, Shogi -> normal */
5371         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5372         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5373         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5374         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5375     } else
5376     if(move[1]=='@' &&
5377        move[3]>='0' && move[3]<='9' &&
5378        move[2]>='a' && move[2]<='x'    ) {
5379         move[1] = '*';
5380         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5381         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5382     } else
5383     if(
5384        move[0]>='a' && move[0]<='x' &&
5385        move[3]>='0' && move[3]<='9' &&
5386        move[2]>='a' && move[2]<='x'    ) {
5387          /* output move, normal -> Shogi */
5388         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5389         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5390         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5391         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5392         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5393     }
5394     if (appData.debugMode) {
5395         fprintf(debugFP, "   out = '%s'\n", move);
5396     }
5397 }
5398
5399 char yy_textstr[8000];
5400
5401 /* Parser for moves from gnuchess, ICS, or user typein box */
5402 Boolean
5403 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5404 {
5405     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5406
5407     switch (*moveType) {
5408       case WhitePromotion:
5409       case BlackPromotion:
5410       case WhiteNonPromotion:
5411       case BlackNonPromotion:
5412       case NormalMove:
5413       case FirstLeg:
5414       case WhiteCapturesEnPassant:
5415       case BlackCapturesEnPassant:
5416       case WhiteKingSideCastle:
5417       case WhiteQueenSideCastle:
5418       case BlackKingSideCastle:
5419       case BlackQueenSideCastle:
5420       case WhiteKingSideCastleWild:
5421       case WhiteQueenSideCastleWild:
5422       case BlackKingSideCastleWild:
5423       case BlackQueenSideCastleWild:
5424       /* Code added by Tord: */
5425       case WhiteHSideCastleFR:
5426       case WhiteASideCastleFR:
5427       case BlackHSideCastleFR:
5428       case BlackASideCastleFR:
5429       /* End of code added by Tord */
5430       case IllegalMove:         /* bug or odd chess variant */
5431         *fromX = currentMoveString[0] - AAA;
5432         *fromY = currentMoveString[1] - ONE;
5433         *toX = currentMoveString[2] - AAA;
5434         *toY = currentMoveString[3] - ONE;
5435         *promoChar = currentMoveString[4];
5436         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5437             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5438     if (appData.debugMode) {
5439         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5440     }
5441             *fromX = *fromY = *toX = *toY = 0;
5442             return FALSE;
5443         }
5444         if (appData.testLegality) {
5445           return (*moveType != IllegalMove);
5446         } else {
5447           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5448                          // [HGM] lion: if this is a double move we are less critical
5449                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5450         }
5451
5452       case WhiteDrop:
5453       case BlackDrop:
5454         *fromX = *moveType == WhiteDrop ?
5455           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5456           (int) CharToPiece(ToLower(currentMoveString[0]));
5457         *fromY = DROP_RANK;
5458         *toX = currentMoveString[2] - AAA;
5459         *toY = currentMoveString[3] - ONE;
5460         *promoChar = NULLCHAR;
5461         return TRUE;
5462
5463       case AmbiguousMove:
5464       case ImpossibleMove:
5465       case EndOfFile:
5466       case ElapsedTime:
5467       case Comment:
5468       case PGNTag:
5469       case NAG:
5470       case WhiteWins:
5471       case BlackWins:
5472       case GameIsDrawn:
5473       default:
5474     if (appData.debugMode) {
5475         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5476     }
5477         /* bug? */
5478         *fromX = *fromY = *toX = *toY = 0;
5479         *promoChar = NULLCHAR;
5480         return FALSE;
5481     }
5482 }
5483
5484 Boolean pushed = FALSE;
5485 char *lastParseAttempt;
5486
5487 void
5488 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5489 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5490   int fromX, fromY, toX, toY; char promoChar;
5491   ChessMove moveType;
5492   Boolean valid;
5493   int nr = 0;
5494
5495   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5496   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5497     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5498     pushed = TRUE;
5499   }
5500   endPV = forwardMostMove;
5501   do {
5502     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5503     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5504     lastParseAttempt = pv;
5505     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5506     if(!valid && nr == 0 &&
5507        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5508         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5509         // Hande case where played move is different from leading PV move
5510         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5511         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5512         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5513         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5514           endPV += 2; // if position different, keep this
5515           moveList[endPV-1][0] = fromX + AAA;
5516           moveList[endPV-1][1] = fromY + ONE;
5517           moveList[endPV-1][2] = toX + AAA;
5518           moveList[endPV-1][3] = toY + ONE;
5519           parseList[endPV-1][0] = NULLCHAR;
5520           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5521         }
5522       }
5523     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5524     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5525     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5526     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5527         valid++; // allow comments in PV
5528         continue;
5529     }
5530     nr++;
5531     if(endPV+1 > framePtr) break; // no space, truncate
5532     if(!valid) break;
5533     endPV++;
5534     CopyBoard(boards[endPV], boards[endPV-1]);
5535     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5536     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5537     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5538     CoordsToAlgebraic(boards[endPV - 1],
5539                              PosFlags(endPV - 1),
5540                              fromY, fromX, toY, toX, promoChar,
5541                              parseList[endPV - 1]);
5542   } while(valid);
5543   if(atEnd == 2) return; // used hidden, for PV conversion
5544   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5545   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5546   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5547                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5548   DrawPosition(TRUE, boards[currentMove]);
5549 }
5550
5551 int
5552 MultiPV (ChessProgramState *cps)
5553 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5554         int i;
5555         for(i=0; i<cps->nrOptions; i++)
5556             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5557                 return i;
5558         return -1;
5559 }
5560
5561 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5562
5563 Boolean
5564 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5565 {
5566         int startPV, multi, lineStart, origIndex = index;
5567         char *p, buf2[MSG_SIZ];
5568         ChessProgramState *cps = (pane ? &second : &first);
5569
5570         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5571         lastX = x; lastY = y;
5572         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5573         lineStart = startPV = index;
5574         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5575         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5576         index = startPV;
5577         do{ while(buf[index] && buf[index] != '\n') index++;
5578         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5579         buf[index] = 0;
5580         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5581                 int n = cps->option[multi].value;
5582                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5583                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5584                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5585                 cps->option[multi].value = n;
5586                 *start = *end = 0;
5587                 return FALSE;
5588         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5589                 ExcludeClick(origIndex - lineStart);
5590                 return FALSE;
5591         }
5592         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5593         *start = startPV; *end = index-1;
5594         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5595         return TRUE;
5596 }
5597
5598 char *
5599 PvToSAN (char *pv)
5600 {
5601         static char buf[10*MSG_SIZ];
5602         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5603         *buf = NULLCHAR;
5604         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5605         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5606         for(i = forwardMostMove; i<endPV; i++){
5607             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5608             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5609             k += strlen(buf+k);
5610         }
5611         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5612         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5613         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5614         endPV = savedEnd;
5615         return buf;
5616 }
5617
5618 Boolean
5619 LoadPV (int x, int y)
5620 { // called on right mouse click to load PV
5621   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5622   lastX = x; lastY = y;
5623   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5624   extendGame = FALSE;
5625   return TRUE;
5626 }
5627
5628 void
5629 UnLoadPV ()
5630 {
5631   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5632   if(endPV < 0) return;
5633   if(appData.autoCopyPV) CopyFENToClipboard();
5634   endPV = -1;
5635   if(extendGame && currentMove > forwardMostMove) {
5636         Boolean saveAnimate = appData.animate;
5637         if(pushed) {
5638             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5639                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5640             } else storedGames--; // abandon shelved tail of original game
5641         }
5642         pushed = FALSE;
5643         forwardMostMove = currentMove;
5644         currentMove = oldFMM;
5645         appData.animate = FALSE;
5646         ToNrEvent(forwardMostMove);
5647         appData.animate = saveAnimate;
5648   }
5649   currentMove = forwardMostMove;
5650   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5651   ClearPremoveHighlights();
5652   DrawPosition(TRUE, boards[currentMove]);
5653 }
5654
5655 void
5656 MovePV (int x, int y, int h)
5657 { // step through PV based on mouse coordinates (called on mouse move)
5658   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5659
5660   // we must somehow check if right button is still down (might be released off board!)
5661   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5662   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5663   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5664   if(!step) return;
5665   lastX = x; lastY = y;
5666
5667   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5668   if(endPV < 0) return;
5669   if(y < margin) step = 1; else
5670   if(y > h - margin) step = -1;
5671   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5672   currentMove += step;
5673   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5674   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5675                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5676   DrawPosition(FALSE, boards[currentMove]);
5677 }
5678
5679
5680 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5681 // All positions will have equal probability, but the current method will not provide a unique
5682 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5683 #define DARK 1
5684 #define LITE 2
5685 #define ANY 3
5686
5687 int squaresLeft[4];
5688 int piecesLeft[(int)BlackPawn];
5689 int seed, nrOfShuffles;
5690
5691 void
5692 GetPositionNumber ()
5693 {       // sets global variable seed
5694         int i;
5695
5696         seed = appData.defaultFrcPosition;
5697         if(seed < 0) { // randomize based on time for negative FRC position numbers
5698                 for(i=0; i<50; i++) seed += random();
5699                 seed = random() ^ random() >> 8 ^ random() << 8;
5700                 if(seed<0) seed = -seed;
5701         }
5702 }
5703
5704 int
5705 put (Board board, int pieceType, int rank, int n, int shade)
5706 // put the piece on the (n-1)-th empty squares of the given shade
5707 {
5708         int i;
5709
5710         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5711                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5712                         board[rank][i] = (ChessSquare) pieceType;
5713                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5714                         squaresLeft[ANY]--;
5715                         piecesLeft[pieceType]--;
5716                         return i;
5717                 }
5718         }
5719         return -1;
5720 }
5721
5722
5723 void
5724 AddOnePiece (Board board, int pieceType, int rank, int shade)
5725 // calculate where the next piece goes, (any empty square), and put it there
5726 {
5727         int i;
5728
5729         i = seed % squaresLeft[shade];
5730         nrOfShuffles *= squaresLeft[shade];
5731         seed /= squaresLeft[shade];
5732         put(board, pieceType, rank, i, shade);
5733 }
5734
5735 void
5736 AddTwoPieces (Board board, int pieceType, int rank)
5737 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5738 {
5739         int i, n=squaresLeft[ANY], j=n-1, k;
5740
5741         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5742         i = seed % k;  // pick one
5743         nrOfShuffles *= k;
5744         seed /= k;
5745         while(i >= j) i -= j--;
5746         j = n - 1 - j; i += j;
5747         put(board, pieceType, rank, j, ANY);
5748         put(board, pieceType, rank, i, ANY);
5749 }
5750
5751 void
5752 SetUpShuffle (Board board, int number)
5753 {
5754         int i, p, first=1;
5755
5756         GetPositionNumber(); nrOfShuffles = 1;
5757
5758         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5759         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5760         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5761
5762         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5763
5764         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5765             p = (int) board[0][i];
5766             if(p < (int) BlackPawn) piecesLeft[p] ++;
5767             board[0][i] = EmptySquare;
5768         }
5769
5770         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5771             // shuffles restricted to allow normal castling put KRR first
5772             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5773                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5774             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5775                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5776             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5777                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5778             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5779                 put(board, WhiteRook, 0, 0, ANY);
5780             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5781         }
5782
5783         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5784             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5785             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5786                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5787                 while(piecesLeft[p] >= 2) {
5788                     AddOnePiece(board, p, 0, LITE);
5789                     AddOnePiece(board, p, 0, DARK);
5790                 }
5791                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5792             }
5793
5794         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5795             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5796             // but we leave King and Rooks for last, to possibly obey FRC restriction
5797             if(p == (int)WhiteRook) continue;
5798             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5799             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5800         }
5801
5802         // now everything is placed, except perhaps King (Unicorn) and Rooks
5803
5804         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5805             // Last King gets castling rights
5806             while(piecesLeft[(int)WhiteUnicorn]) {
5807                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5808                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5809             }
5810
5811             while(piecesLeft[(int)WhiteKing]) {
5812                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5813                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5814             }
5815
5816
5817         } else {
5818             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5819             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5820         }
5821
5822         // Only Rooks can be left; simply place them all
5823         while(piecesLeft[(int)WhiteRook]) {
5824                 i = put(board, WhiteRook, 0, 0, ANY);
5825                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5826                         if(first) {
5827                                 first=0;
5828                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5829                         }
5830                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5831                 }
5832         }
5833         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5834             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5835         }
5836
5837         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5838 }
5839
5840 int
5841 SetCharTable (char *table, const char * map)
5842 /* [HGM] moved here from winboard.c because of its general usefulness */
5843 /*       Basically a safe strcpy that uses the last character as King */
5844 {
5845     int result = FALSE; int NrPieces;
5846
5847     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5848                     && NrPieces >= 12 && !(NrPieces&1)) {
5849         int i; /* [HGM] Accept even length from 12 to 34 */
5850
5851         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5852         for( i=0; i<NrPieces/2-1; i++ ) {
5853             table[i] = map[i];
5854             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5855         }
5856         table[(int) WhiteKing]  = map[NrPieces/2-1];
5857         table[(int) BlackKing]  = map[NrPieces-1];
5858
5859         result = TRUE;
5860     }
5861
5862     return result;
5863 }
5864
5865 void
5866 Prelude (Board board)
5867 {       // [HGM] superchess: random selection of exo-pieces
5868         int i, j, k; ChessSquare p;
5869         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5870
5871         GetPositionNumber(); // use FRC position number
5872
5873         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5874             SetCharTable(pieceToChar, appData.pieceToCharTable);
5875             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5876                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5877         }
5878
5879         j = seed%4;                 seed /= 4;
5880         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5881         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5882         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5883         j = seed%3 + (seed%3 >= j); seed /= 3;
5884         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5885         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5886         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5887         j = seed%3;                 seed /= 3;
5888         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5889         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5890         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5891         j = seed%2 + (seed%2 >= j); seed /= 2;
5892         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5893         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5894         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5895         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5896         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5897         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5898         put(board, exoPieces[0],    0, 0, ANY);
5899         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5900 }
5901
5902 void
5903 InitPosition (int redraw)
5904 {
5905     ChessSquare (* pieces)[BOARD_FILES];
5906     int i, j, pawnRow=1, pieceRows=1, overrule,
5907     oldx = gameInfo.boardWidth,
5908     oldy = gameInfo.boardHeight,
5909     oldh = gameInfo.holdingsWidth;
5910     static int oldv;
5911
5912     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5913
5914     /* [AS] Initialize pv info list [HGM] and game status */
5915     {
5916         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5917             pvInfoList[i].depth = 0;
5918             boards[i][EP_STATUS] = EP_NONE;
5919             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5920         }
5921
5922         initialRulePlies = 0; /* 50-move counter start */
5923
5924         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5925         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5926     }
5927
5928
5929     /* [HGM] logic here is completely changed. In stead of full positions */
5930     /* the initialized data only consist of the two backranks. The switch */
5931     /* selects which one we will use, which is than copied to the Board   */
5932     /* initialPosition, which for the rest is initialized by Pawns and    */
5933     /* empty squares. This initial position is then copied to boards[0],  */
5934     /* possibly after shuffling, so that it remains available.            */
5935
5936     gameInfo.holdingsWidth = 0; /* default board sizes */
5937     gameInfo.boardWidth    = 8;
5938     gameInfo.boardHeight   = 8;
5939     gameInfo.holdingsSize  = 0;
5940     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5941     for(i=0; i<BOARD_FILES-2; i++)
5942       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5943     initialPosition[EP_STATUS] = EP_NONE;
5944     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5945     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5946          SetCharTable(pieceNickName, appData.pieceNickNames);
5947     else SetCharTable(pieceNickName, "............");
5948     pieces = FIDEArray;
5949
5950     switch (gameInfo.variant) {
5951     case VariantFischeRandom:
5952       shuffleOpenings = TRUE;
5953     default:
5954       break;
5955     case VariantShatranj:
5956       pieces = ShatranjArray;
5957       nrCastlingRights = 0;
5958       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5959       break;
5960     case VariantMakruk:
5961       pieces = makrukArray;
5962       nrCastlingRights = 0;
5963       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5964       break;
5965     case VariantASEAN:
5966       pieces = aseanArray;
5967       nrCastlingRights = 0;
5968       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5969       break;
5970     case VariantTwoKings:
5971       pieces = twoKingsArray;
5972       break;
5973     case VariantGrand:
5974       pieces = GrandArray;
5975       nrCastlingRights = 0;
5976       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5977       gameInfo.boardWidth = 10;
5978       gameInfo.boardHeight = 10;
5979       gameInfo.holdingsSize = 7;
5980       break;
5981     case VariantCapaRandom:
5982       shuffleOpenings = TRUE;
5983     case VariantCapablanca:
5984       pieces = CapablancaArray;
5985       gameInfo.boardWidth = 10;
5986       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5987       break;
5988     case VariantGothic:
5989       pieces = GothicArray;
5990       gameInfo.boardWidth = 10;
5991       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5992       break;
5993     case VariantSChess:
5994       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5995       gameInfo.holdingsSize = 7;
5996       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5997       break;
5998     case VariantJanus:
5999       pieces = JanusArray;
6000       gameInfo.boardWidth = 10;
6001       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6002       nrCastlingRights = 6;
6003         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6004         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6005         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6006         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6007         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6008         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6009       break;
6010     case VariantFalcon:
6011       pieces = FalconArray;
6012       gameInfo.boardWidth = 10;
6013       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6014       break;
6015     case VariantXiangqi:
6016       pieces = XiangqiArray;
6017       gameInfo.boardWidth  = 9;
6018       gameInfo.boardHeight = 10;
6019       nrCastlingRights = 0;
6020       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6021       break;
6022     case VariantShogi:
6023       pieces = ShogiArray;
6024       gameInfo.boardWidth  = 9;
6025       gameInfo.boardHeight = 9;
6026       gameInfo.holdingsSize = 7;
6027       nrCastlingRights = 0;
6028       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6029       break;
6030     case VariantChu:
6031       pieces = ChuArray; pieceRows = 3;
6032       gameInfo.boardWidth  = 12;
6033       gameInfo.boardHeight = 12;
6034       nrCastlingRights = 0;
6035       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6036                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6037       break;
6038     case VariantCourier:
6039       pieces = CourierArray;
6040       gameInfo.boardWidth  = 12;
6041       nrCastlingRights = 0;
6042       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6043       break;
6044     case VariantKnightmate:
6045       pieces = KnightmateArray;
6046       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6047       break;
6048     case VariantSpartan:
6049       pieces = SpartanArray;
6050       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6051       break;
6052     case VariantLion:
6053       pieces = lionArray;
6054       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6055       break;
6056     case VariantFairy:
6057       pieces = fairyArray;
6058       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6059       break;
6060     case VariantGreat:
6061       pieces = GreatArray;
6062       gameInfo.boardWidth = 10;
6063       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6064       gameInfo.holdingsSize = 8;
6065       break;
6066     case VariantSuper:
6067       pieces = FIDEArray;
6068       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6069       gameInfo.holdingsSize = 8;
6070       startedFromSetupPosition = TRUE;
6071       break;
6072     case VariantCrazyhouse:
6073     case VariantBughouse:
6074       pieces = FIDEArray;
6075       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6076       gameInfo.holdingsSize = 5;
6077       break;
6078     case VariantWildCastle:
6079       pieces = FIDEArray;
6080       /* !!?shuffle with kings guaranteed to be on d or e file */
6081       shuffleOpenings = 1;
6082       break;
6083     case VariantNoCastle:
6084       pieces = FIDEArray;
6085       nrCastlingRights = 0;
6086       /* !!?unconstrained back-rank shuffle */
6087       shuffleOpenings = 1;
6088       break;
6089     }
6090
6091     overrule = 0;
6092     if(appData.NrFiles >= 0) {
6093         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6094         gameInfo.boardWidth = appData.NrFiles;
6095     }
6096     if(appData.NrRanks >= 0) {
6097         gameInfo.boardHeight = appData.NrRanks;
6098     }
6099     if(appData.holdingsSize >= 0) {
6100         i = appData.holdingsSize;
6101         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6102         gameInfo.holdingsSize = i;
6103     }
6104     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6105     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6106         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6107
6108     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6109     if(pawnRow < 1) pawnRow = 1;
6110     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6111     if(gameInfo.variant == VariantChu) pawnRow = 3;
6112
6113     /* User pieceToChar list overrules defaults */
6114     if(appData.pieceToCharTable != NULL)
6115         SetCharTable(pieceToChar, appData.pieceToCharTable);
6116
6117     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6118
6119         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6120             s = (ChessSquare) 0; /* account holding counts in guard band */
6121         for( i=0; i<BOARD_HEIGHT; i++ )
6122             initialPosition[i][j] = s;
6123
6124         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6125         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6126         initialPosition[pawnRow][j] = WhitePawn;
6127         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6128         if(gameInfo.variant == VariantXiangqi) {
6129             if(j&1) {
6130                 initialPosition[pawnRow][j] =
6131                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6132                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6133                    initialPosition[2][j] = WhiteCannon;
6134                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6135                 }
6136             }
6137         }
6138         if(gameInfo.variant == VariantChu) {
6139              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6140                initialPosition[pawnRow+1][j] = WhiteCobra,
6141                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6142              for(i=1; i<pieceRows; i++) {
6143                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6144                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6145              }
6146         }
6147         if(gameInfo.variant == VariantGrand) {
6148             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6149                initialPosition[0][j] = WhiteRook;
6150                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6151             }
6152         }
6153         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6154     }
6155     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6156
6157             j=BOARD_LEFT+1;
6158             initialPosition[1][j] = WhiteBishop;
6159             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6160             j=BOARD_RGHT-2;
6161             initialPosition[1][j] = WhiteRook;
6162             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6163     }
6164
6165     if( nrCastlingRights == -1) {
6166         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6167         /*       This sets default castling rights from none to normal corners   */
6168         /* Variants with other castling rights must set them themselves above    */
6169         nrCastlingRights = 6;
6170
6171         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6172         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6173         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6174         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6175         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6176         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6177      }
6178
6179      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6180      if(gameInfo.variant == VariantGreat) { // promotion commoners
6181         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6182         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6183         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6184         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6185      }
6186      if( gameInfo.variant == VariantSChess ) {
6187       initialPosition[1][0] = BlackMarshall;
6188       initialPosition[2][0] = BlackAngel;
6189       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6190       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6191       initialPosition[1][1] = initialPosition[2][1] =
6192       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6193      }
6194   if (appData.debugMode) {
6195     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6196   }
6197     if(shuffleOpenings) {
6198         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6199         startedFromSetupPosition = TRUE;
6200     }
6201     if(startedFromPositionFile) {
6202       /* [HGM] loadPos: use PositionFile for every new game */
6203       CopyBoard(initialPosition, filePosition);
6204       for(i=0; i<nrCastlingRights; i++)
6205           initialRights[i] = filePosition[CASTLING][i];
6206       startedFromSetupPosition = TRUE;
6207     }
6208
6209     CopyBoard(boards[0], initialPosition);
6210
6211     if(oldx != gameInfo.boardWidth ||
6212        oldy != gameInfo.boardHeight ||
6213        oldv != gameInfo.variant ||
6214        oldh != gameInfo.holdingsWidth
6215                                          )
6216             InitDrawingSizes(-2 ,0);
6217
6218     oldv = gameInfo.variant;
6219     if (redraw)
6220       DrawPosition(TRUE, boards[currentMove]);
6221 }
6222
6223 void
6224 SendBoard (ChessProgramState *cps, int moveNum)
6225 {
6226     char message[MSG_SIZ];
6227
6228     if (cps->useSetboard) {
6229       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6230       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6231       SendToProgram(message, cps);
6232       free(fen);
6233
6234     } else {
6235       ChessSquare *bp;
6236       int i, j, left=0, right=BOARD_WIDTH;
6237       /* Kludge to set black to move, avoiding the troublesome and now
6238        * deprecated "black" command.
6239        */
6240       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6241         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6242
6243       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6244
6245       SendToProgram("edit\n", cps);
6246       SendToProgram("#\n", cps);
6247       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6248         bp = &boards[moveNum][i][left];
6249         for (j = left; j < right; j++, bp++) {
6250           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6251           if ((int) *bp < (int) BlackPawn) {
6252             if(j == BOARD_RGHT+1)
6253                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6254             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6255             if(message[0] == '+' || message[0] == '~') {
6256               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6257                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6258                         AAA + j, ONE + i);
6259             }
6260             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6261                 message[1] = BOARD_RGHT   - 1 - j + '1';
6262                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6263             }
6264             SendToProgram(message, cps);
6265           }
6266         }
6267       }
6268
6269       SendToProgram("c\n", cps);
6270       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6271         bp = &boards[moveNum][i][left];
6272         for (j = left; j < right; j++, bp++) {
6273           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6274           if (((int) *bp != (int) EmptySquare)
6275               && ((int) *bp >= (int) BlackPawn)) {
6276             if(j == BOARD_LEFT-2)
6277                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6278             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6279                     AAA + j, ONE + i);
6280             if(message[0] == '+' || message[0] == '~') {
6281               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6282                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6283                         AAA + j, ONE + i);
6284             }
6285             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6286                 message[1] = BOARD_RGHT   - 1 - j + '1';
6287                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6288             }
6289             SendToProgram(message, cps);
6290           }
6291         }
6292       }
6293
6294       SendToProgram(".\n", cps);
6295     }
6296     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6297 }
6298
6299 char exclusionHeader[MSG_SIZ];
6300 int exCnt, excludePtr;
6301 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6302 static Exclusion excluTab[200];
6303 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6304
6305 static void
6306 WriteMap (int s)
6307 {
6308     int j;
6309     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6310     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6311 }
6312
6313 static void
6314 ClearMap ()
6315 {
6316     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6317     excludePtr = 24; exCnt = 0;
6318     WriteMap(0);
6319 }
6320
6321 static void
6322 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6323 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6324     char buf[2*MOVE_LEN], *p;
6325     Exclusion *e = excluTab;
6326     int i;
6327     for(i=0; i<exCnt; i++)
6328         if(e[i].ff == fromX && e[i].fr == fromY &&
6329            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6330     if(i == exCnt) { // was not in exclude list; add it
6331         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6332         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6333             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6334             return; // abort
6335         }
6336         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6337         excludePtr++; e[i].mark = excludePtr++;
6338         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6339         exCnt++;
6340     }
6341     exclusionHeader[e[i].mark] = state;
6342 }
6343
6344 static int
6345 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6346 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6347     char buf[MSG_SIZ];
6348     int j, k;
6349     ChessMove moveType;
6350     if((signed char)promoChar == -1) { // kludge to indicate best move
6351         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6352             return 1; // if unparsable, abort
6353     }
6354     // update exclusion map (resolving toggle by consulting existing state)
6355     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6356     j = k%8; k >>= 3;
6357     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6358     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6359          excludeMap[k] |=   1<<j;
6360     else excludeMap[k] &= ~(1<<j);
6361     // update header
6362     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6363     // inform engine
6364     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6365     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6366     SendToBoth(buf);
6367     return (state == '+');
6368 }
6369
6370 static void
6371 ExcludeClick (int index)
6372 {
6373     int i, j;
6374     Exclusion *e = excluTab;
6375     if(index < 25) { // none, best or tail clicked
6376         if(index < 13) { // none: include all
6377             WriteMap(0); // clear map
6378             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6379             SendToBoth("include all\n"); // and inform engine
6380         } else if(index > 18) { // tail
6381             if(exclusionHeader[19] == '-') { // tail was excluded
6382                 SendToBoth("include all\n");
6383                 WriteMap(0); // clear map completely
6384                 // now re-exclude selected moves
6385                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6386                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6387             } else { // tail was included or in mixed state
6388                 SendToBoth("exclude all\n");
6389                 WriteMap(0xFF); // fill map completely
6390                 // now re-include selected moves
6391                 j = 0; // count them
6392                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6393                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6394                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6395             }
6396         } else { // best
6397             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6398         }
6399     } else {
6400         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6401             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6402             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6403             break;
6404         }
6405     }
6406 }
6407
6408 ChessSquare
6409 DefaultPromoChoice (int white)
6410 {
6411     ChessSquare result;
6412     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6413        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6414         result = WhiteFerz; // no choice
6415     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6416         result= WhiteKing; // in Suicide Q is the last thing we want
6417     else if(gameInfo.variant == VariantSpartan)
6418         result = white ? WhiteQueen : WhiteAngel;
6419     else result = WhiteQueen;
6420     if(!white) result = WHITE_TO_BLACK result;
6421     return result;
6422 }
6423
6424 static int autoQueen; // [HGM] oneclick
6425
6426 int
6427 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6428 {
6429     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6430     /* [HGM] add Shogi promotions */
6431     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6432     ChessSquare piece;
6433     ChessMove moveType;
6434     Boolean premove;
6435
6436     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6437     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6438
6439     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6440       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6441         return FALSE;
6442
6443     piece = boards[currentMove][fromY][fromX];
6444     if(gameInfo.variant == VariantChu) {
6445         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6446         promotionZoneSize = BOARD_HEIGHT/3;
6447         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6448     } else if(gameInfo.variant == VariantShogi) {
6449         promotionZoneSize = BOARD_HEIGHT/3;
6450         highestPromotingPiece = (int)WhiteAlfil;
6451     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6452         promotionZoneSize = 3;
6453     }
6454
6455     // Treat Lance as Pawn when it is not representing Amazon
6456     if(gameInfo.variant != VariantSuper) {
6457         if(piece == WhiteLance) piece = WhitePawn; else
6458         if(piece == BlackLance) piece = BlackPawn;
6459     }
6460
6461     // next weed out all moves that do not touch the promotion zone at all
6462     if((int)piece >= BlackPawn) {
6463         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6464              return FALSE;
6465         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6466     } else {
6467         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6468            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6469     }
6470
6471     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6472
6473     // weed out mandatory Shogi promotions
6474     if(gameInfo.variant == VariantShogi) {
6475         if(piece >= BlackPawn) {
6476             if(toY == 0 && piece == BlackPawn ||
6477                toY == 0 && piece == BlackQueen ||
6478                toY <= 1 && piece == BlackKnight) {
6479                 *promoChoice = '+';
6480                 return FALSE;
6481             }
6482         } else {
6483             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6484                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6485                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6486                 *promoChoice = '+';
6487                 return FALSE;
6488             }
6489         }
6490     }
6491
6492     // weed out obviously illegal Pawn moves
6493     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6494         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6495         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6496         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6497         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6498         // note we are not allowed to test for valid (non-)capture, due to premove
6499     }
6500
6501     // we either have a choice what to promote to, or (in Shogi) whether to promote
6502     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6503        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6504         *promoChoice = PieceToChar(BlackFerz);  // no choice
6505         return FALSE;
6506     }
6507     // no sense asking what we must promote to if it is going to explode...
6508     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6509         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6510         return FALSE;
6511     }
6512     // give caller the default choice even if we will not make it
6513     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6514     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6515     if(        sweepSelect && gameInfo.variant != VariantGreat
6516                            && gameInfo.variant != VariantGrand
6517                            && gameInfo.variant != VariantSuper) return FALSE;
6518     if(autoQueen) return FALSE; // predetermined
6519
6520     // suppress promotion popup on illegal moves that are not premoves
6521     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6522               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6523     if(appData.testLegality && !premove) {
6524         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6525                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6526         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6527             return FALSE;
6528     }
6529
6530     return TRUE;
6531 }
6532
6533 int
6534 InPalace (int row, int column)
6535 {   /* [HGM] for Xiangqi */
6536     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6537          column < (BOARD_WIDTH + 4)/2 &&
6538          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6539     return FALSE;
6540 }
6541
6542 int
6543 PieceForSquare (int x, int y)
6544 {
6545   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6546      return -1;
6547   else
6548      return boards[currentMove][y][x];
6549 }
6550
6551 int
6552 OKToStartUserMove (int x, int y)
6553 {
6554     ChessSquare from_piece;
6555     int white_piece;
6556
6557     if (matchMode) return FALSE;
6558     if (gameMode == EditPosition) return TRUE;
6559
6560     if (x >= 0 && y >= 0)
6561       from_piece = boards[currentMove][y][x];
6562     else
6563       from_piece = EmptySquare;
6564
6565     if (from_piece == EmptySquare) return FALSE;
6566
6567     white_piece = (int)from_piece >= (int)WhitePawn &&
6568       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6569
6570     switch (gameMode) {
6571       case AnalyzeFile:
6572       case TwoMachinesPlay:
6573       case EndOfGame:
6574         return FALSE;
6575
6576       case IcsObserving:
6577       case IcsIdle:
6578         return FALSE;
6579
6580       case MachinePlaysWhite:
6581       case IcsPlayingBlack:
6582         if (appData.zippyPlay) return FALSE;
6583         if (white_piece) {
6584             DisplayMoveError(_("You are playing Black"));
6585             return FALSE;
6586         }
6587         break;
6588
6589       case MachinePlaysBlack:
6590       case IcsPlayingWhite:
6591         if (appData.zippyPlay) return FALSE;
6592         if (!white_piece) {
6593             DisplayMoveError(_("You are playing White"));
6594             return FALSE;
6595         }
6596         break;
6597
6598       case PlayFromGameFile:
6599             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6600       case EditGame:
6601         if (!white_piece && WhiteOnMove(currentMove)) {
6602             DisplayMoveError(_("It is White's turn"));
6603             return FALSE;
6604         }
6605         if (white_piece && !WhiteOnMove(currentMove)) {
6606             DisplayMoveError(_("It is Black's turn"));
6607             return FALSE;
6608         }
6609         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6610             /* Editing correspondence game history */
6611             /* Could disallow this or prompt for confirmation */
6612             cmailOldMove = -1;
6613         }
6614         break;
6615
6616       case BeginningOfGame:
6617         if (appData.icsActive) return FALSE;
6618         if (!appData.noChessProgram) {
6619             if (!white_piece) {
6620                 DisplayMoveError(_("You are playing White"));
6621                 return FALSE;
6622             }
6623         }
6624         break;
6625
6626       case Training:
6627         if (!white_piece && WhiteOnMove(currentMove)) {
6628             DisplayMoveError(_("It is White's turn"));
6629             return FALSE;
6630         }
6631         if (white_piece && !WhiteOnMove(currentMove)) {
6632             DisplayMoveError(_("It is Black's turn"));
6633             return FALSE;
6634         }
6635         break;
6636
6637       default:
6638       case IcsExamining:
6639         break;
6640     }
6641     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6642         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6643         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6644         && gameMode != AnalyzeFile && gameMode != Training) {
6645         DisplayMoveError(_("Displayed position is not current"));
6646         return FALSE;
6647     }
6648     return TRUE;
6649 }
6650
6651 Boolean
6652 OnlyMove (int *x, int *y, Boolean captures)
6653 {
6654     DisambiguateClosure cl;
6655     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6656     switch(gameMode) {
6657       case MachinePlaysBlack:
6658       case IcsPlayingWhite:
6659       case BeginningOfGame:
6660         if(!WhiteOnMove(currentMove)) return FALSE;
6661         break;
6662       case MachinePlaysWhite:
6663       case IcsPlayingBlack:
6664         if(WhiteOnMove(currentMove)) return FALSE;
6665         break;
6666       case EditGame:
6667         break;
6668       default:
6669         return FALSE;
6670     }
6671     cl.pieceIn = EmptySquare;
6672     cl.rfIn = *y;
6673     cl.ffIn = *x;
6674     cl.rtIn = -1;
6675     cl.ftIn = -1;
6676     cl.promoCharIn = NULLCHAR;
6677     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6678     if( cl.kind == NormalMove ||
6679         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6680         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6681         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6682       fromX = cl.ff;
6683       fromY = cl.rf;
6684       *x = cl.ft;
6685       *y = cl.rt;
6686       return TRUE;
6687     }
6688     if(cl.kind != ImpossibleMove) return FALSE;
6689     cl.pieceIn = EmptySquare;
6690     cl.rfIn = -1;
6691     cl.ffIn = -1;
6692     cl.rtIn = *y;
6693     cl.ftIn = *x;
6694     cl.promoCharIn = NULLCHAR;
6695     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6696     if( cl.kind == NormalMove ||
6697         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6698         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6699         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6700       fromX = cl.ff;
6701       fromY = cl.rf;
6702       *x = cl.ft;
6703       *y = cl.rt;
6704       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6705       return TRUE;
6706     }
6707     return FALSE;
6708 }
6709
6710 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6711 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6712 int lastLoadGameUseList = FALSE;
6713 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6714 ChessMove lastLoadGameStart = EndOfFile;
6715 int doubleClick;
6716
6717 void
6718 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6719 {
6720     ChessMove moveType;
6721     ChessSquare pup;
6722     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6723
6724     /* Check if the user is playing in turn.  This is complicated because we
6725        let the user "pick up" a piece before it is his turn.  So the piece he
6726        tried to pick up may have been captured by the time he puts it down!
6727        Therefore we use the color the user is supposed to be playing in this
6728        test, not the color of the piece that is currently on the starting
6729        square---except in EditGame mode, where the user is playing both
6730        sides; fortunately there the capture race can't happen.  (It can
6731        now happen in IcsExamining mode, but that's just too bad.  The user
6732        will get a somewhat confusing message in that case.)
6733        */
6734
6735     switch (gameMode) {
6736       case AnalyzeFile:
6737       case TwoMachinesPlay:
6738       case EndOfGame:
6739       case IcsObserving:
6740       case IcsIdle:
6741         /* We switched into a game mode where moves are not accepted,
6742            perhaps while the mouse button was down. */
6743         return;
6744
6745       case MachinePlaysWhite:
6746         /* User is moving for Black */
6747         if (WhiteOnMove(currentMove)) {
6748             DisplayMoveError(_("It is White's turn"));
6749             return;
6750         }
6751         break;
6752
6753       case MachinePlaysBlack:
6754         /* User is moving for White */
6755         if (!WhiteOnMove(currentMove)) {
6756             DisplayMoveError(_("It is Black's turn"));
6757             return;
6758         }
6759         break;
6760
6761       case PlayFromGameFile:
6762             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6763       case EditGame:
6764       case IcsExamining:
6765       case BeginningOfGame:
6766       case AnalyzeMode:
6767       case Training:
6768         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6769         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6770             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6771             /* User is moving for Black */
6772             if (WhiteOnMove(currentMove)) {
6773                 DisplayMoveError(_("It is White's turn"));
6774                 return;
6775             }
6776         } else {
6777             /* User is moving for White */
6778             if (!WhiteOnMove(currentMove)) {
6779                 DisplayMoveError(_("It is Black's turn"));
6780                 return;
6781             }
6782         }
6783         break;
6784
6785       case IcsPlayingBlack:
6786         /* User is moving for Black */
6787         if (WhiteOnMove(currentMove)) {
6788             if (!appData.premove) {
6789                 DisplayMoveError(_("It is White's turn"));
6790             } else if (toX >= 0 && toY >= 0) {
6791                 premoveToX = toX;
6792                 premoveToY = toY;
6793                 premoveFromX = fromX;
6794                 premoveFromY = fromY;
6795                 premovePromoChar = promoChar;
6796                 gotPremove = 1;
6797                 if (appData.debugMode)
6798                     fprintf(debugFP, "Got premove: fromX %d,"
6799                             "fromY %d, toX %d, toY %d\n",
6800                             fromX, fromY, toX, toY);
6801             }
6802             return;
6803         }
6804         break;
6805
6806       case IcsPlayingWhite:
6807         /* User is moving for White */
6808         if (!WhiteOnMove(currentMove)) {
6809             if (!appData.premove) {
6810                 DisplayMoveError(_("It is Black's turn"));
6811             } else if (toX >= 0 && toY >= 0) {
6812                 premoveToX = toX;
6813                 premoveToY = toY;
6814                 premoveFromX = fromX;
6815                 premoveFromY = fromY;
6816                 premovePromoChar = promoChar;
6817                 gotPremove = 1;
6818                 if (appData.debugMode)
6819                     fprintf(debugFP, "Got premove: fromX %d,"
6820                             "fromY %d, toX %d, toY %d\n",
6821                             fromX, fromY, toX, toY);
6822             }
6823             return;
6824         }
6825         break;
6826
6827       default:
6828         break;
6829
6830       case EditPosition:
6831         /* EditPosition, empty square, or different color piece;
6832            click-click move is possible */
6833         if (toX == -2 || toY == -2) {
6834             boards[0][fromY][fromX] = EmptySquare;
6835             DrawPosition(FALSE, boards[currentMove]);
6836             return;
6837         } else if (toX >= 0 && toY >= 0) {
6838             boards[0][toY][toX] = boards[0][fromY][fromX];
6839             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6840                 if(boards[0][fromY][0] != EmptySquare) {
6841                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6842                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6843                 }
6844             } else
6845             if(fromX == BOARD_RGHT+1) {
6846                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6847                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6848                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6849                 }
6850             } else
6851             boards[0][fromY][fromX] = gatingPiece;
6852             DrawPosition(FALSE, boards[currentMove]);
6853             return;
6854         }
6855         return;
6856     }
6857
6858     if(toX < 0 || toY < 0) return;
6859     pup = boards[currentMove][toY][toX];
6860
6861     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6862     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6863          if( pup != EmptySquare ) return;
6864          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6865            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6866                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6867            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6868            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6869            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6870            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6871          fromY = DROP_RANK;
6872     }
6873
6874     /* [HGM] always test for legality, to get promotion info */
6875     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6876                                          fromY, fromX, toY, toX, promoChar);
6877
6878     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6879
6880     /* [HGM] but possibly ignore an IllegalMove result */
6881     if (appData.testLegality) {
6882         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6883             DisplayMoveError(_("Illegal move"));
6884             return;
6885         }
6886     }
6887
6888     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6889         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6890              ClearPremoveHighlights(); // was included
6891         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6892         return;
6893     }
6894
6895     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6896 }
6897
6898 /* Common tail of UserMoveEvent and DropMenuEvent */
6899 int
6900 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6901 {
6902     char *bookHit = 0;
6903
6904     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6905         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6906         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6907         if(WhiteOnMove(currentMove)) {
6908             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6909         } else {
6910             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6911         }
6912     }
6913
6914     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6915        move type in caller when we know the move is a legal promotion */
6916     if(moveType == NormalMove && promoChar)
6917         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6918
6919     /* [HGM] <popupFix> The following if has been moved here from
6920        UserMoveEvent(). Because it seemed to belong here (why not allow
6921        piece drops in training games?), and because it can only be
6922        performed after it is known to what we promote. */
6923     if (gameMode == Training) {
6924       /* compare the move played on the board to the next move in the
6925        * game. If they match, display the move and the opponent's response.
6926        * If they don't match, display an error message.
6927        */
6928       int saveAnimate;
6929       Board testBoard;
6930       CopyBoard(testBoard, boards[currentMove]);
6931       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6932
6933       if (CompareBoards(testBoard, boards[currentMove+1])) {
6934         ForwardInner(currentMove+1);
6935
6936         /* Autoplay the opponent's response.
6937          * if appData.animate was TRUE when Training mode was entered,
6938          * the response will be animated.
6939          */
6940         saveAnimate = appData.animate;
6941         appData.animate = animateTraining;
6942         ForwardInner(currentMove+1);
6943         appData.animate = saveAnimate;
6944
6945         /* check for the end of the game */
6946         if (currentMove >= forwardMostMove) {
6947           gameMode = PlayFromGameFile;
6948           ModeHighlight();
6949           SetTrainingModeOff();
6950           DisplayInformation(_("End of game"));
6951         }
6952       } else {
6953         DisplayError(_("Incorrect move"), 0);
6954       }
6955       return 1;
6956     }
6957
6958   /* Ok, now we know that the move is good, so we can kill
6959      the previous line in Analysis Mode */
6960   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6961                                 && currentMove < forwardMostMove) {
6962     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6963     else forwardMostMove = currentMove;
6964   }
6965
6966   ClearMap();
6967
6968   /* If we need the chess program but it's dead, restart it */
6969   ResurrectChessProgram();
6970
6971   /* A user move restarts a paused game*/
6972   if (pausing)
6973     PauseEvent();
6974
6975   thinkOutput[0] = NULLCHAR;
6976
6977   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6978
6979   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6980     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6981     return 1;
6982   }
6983
6984   if (gameMode == BeginningOfGame) {
6985     if (appData.noChessProgram) {
6986       gameMode = EditGame;
6987       SetGameInfo();
6988     } else {
6989       char buf[MSG_SIZ];
6990       gameMode = MachinePlaysBlack;
6991       StartClocks();
6992       SetGameInfo();
6993       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6994       DisplayTitle(buf);
6995       if (first.sendName) {
6996         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6997         SendToProgram(buf, &first);
6998       }
6999       StartClocks();
7000     }
7001     ModeHighlight();
7002   }
7003
7004   /* Relay move to ICS or chess engine */
7005   if (appData.icsActive) {
7006     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7007         gameMode == IcsExamining) {
7008       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7009         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7010         SendToICS("draw ");
7011         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7012       }
7013       // also send plain move, in case ICS does not understand atomic claims
7014       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7015       ics_user_moved = 1;
7016     }
7017   } else {
7018     if (first.sendTime && (gameMode == BeginningOfGame ||
7019                            gameMode == MachinePlaysWhite ||
7020                            gameMode == MachinePlaysBlack)) {
7021       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7022     }
7023     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7024          // [HGM] book: if program might be playing, let it use book
7025         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7026         first.maybeThinking = TRUE;
7027     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7028         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7029         SendBoard(&first, currentMove+1);
7030         if(second.analyzing) {
7031             if(!second.useSetboard) SendToProgram("undo\n", &second);
7032             SendBoard(&second, currentMove+1);
7033         }
7034     } else {
7035         SendMoveToProgram(forwardMostMove-1, &first);
7036         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7037     }
7038     if (currentMove == cmailOldMove + 1) {
7039       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7040     }
7041   }
7042
7043   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7044
7045   switch (gameMode) {
7046   case EditGame:
7047     if(appData.testLegality)
7048     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7049     case MT_NONE:
7050     case MT_CHECK:
7051       break;
7052     case MT_CHECKMATE:
7053     case MT_STAINMATE:
7054       if (WhiteOnMove(currentMove)) {
7055         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7056       } else {
7057         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7058       }
7059       break;
7060     case MT_STALEMATE:
7061       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7062       break;
7063     }
7064     break;
7065
7066   case MachinePlaysBlack:
7067   case MachinePlaysWhite:
7068     /* disable certain menu options while machine is thinking */
7069     SetMachineThinkingEnables();
7070     break;
7071
7072   default:
7073     break;
7074   }
7075
7076   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7077   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7078
7079   if(bookHit) { // [HGM] book: simulate book reply
7080         static char bookMove[MSG_SIZ]; // a bit generous?
7081
7082         programStats.nodes = programStats.depth = programStats.time =
7083         programStats.score = programStats.got_only_move = 0;
7084         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7085
7086         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7087         strcat(bookMove, bookHit);
7088         HandleMachineMove(bookMove, &first);
7089   }
7090   return 1;
7091 }
7092
7093 void
7094 MarkByFEN(char *fen)
7095 {
7096         int r, f;
7097         if(!appData.markers || !appData.highlightDragging) return;
7098         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7099         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7100         while(*fen) {
7101             int s = 0;
7102             marker[r][f] = 0;
7103             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7104             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7105             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7106             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7107             if(*fen == 'T') marker[r][f++] = 0; else
7108             if(*fen == 'Y') marker[r][f++] = 1; else
7109             if(*fen == 'G') marker[r][f++] = 3; else
7110             if(*fen == 'B') marker[r][f++] = 4; else
7111             if(*fen == 'C') marker[r][f++] = 5; else
7112             if(*fen == 'M') marker[r][f++] = 6; else
7113             if(*fen == 'W') marker[r][f++] = 7; else
7114             if(*fen == 'D') marker[r][f++] = 8; else
7115             if(*fen == 'R') marker[r][f++] = 2; else {
7116                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7117               f += s; fen -= s>0;
7118             }
7119             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7120             if(r < 0) break;
7121             fen++;
7122         }
7123         DrawPosition(TRUE, NULL);
7124 }
7125
7126 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7127
7128 void
7129 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7130 {
7131     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7132     Markers *m = (Markers *) closure;
7133     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7134         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7135                          || kind == WhiteCapturesEnPassant
7136                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7137     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7138 }
7139
7140 void
7141 MarkTargetSquares (int clear)
7142 {
7143   int x, y, sum=0;
7144   if(clear) { // no reason to ever suppress clearing
7145     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7146     if(!sum) return; // nothing was cleared,no redraw needed
7147   } else {
7148     int capt = 0;
7149     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7150        !appData.testLegality || gameMode == EditPosition) return;
7151     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7152     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7153       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7154       if(capt)
7155       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7156     }
7157   }
7158   DrawPosition(FALSE, NULL);
7159 }
7160
7161 int
7162 Explode (Board board, int fromX, int fromY, int toX, int toY)
7163 {
7164     if(gameInfo.variant == VariantAtomic &&
7165        (board[toY][toX] != EmptySquare ||                     // capture?
7166         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7167                          board[fromY][fromX] == BlackPawn   )
7168       )) {
7169         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7170         return TRUE;
7171     }
7172     return FALSE;
7173 }
7174
7175 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7176
7177 int
7178 CanPromote (ChessSquare piece, int y)
7179 {
7180         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7181         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7182         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7183            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7184            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7185          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7186         return (piece == BlackPawn && y == 1 ||
7187                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7188                 piece == BlackLance && y == 1 ||
7189                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7190 }
7191
7192 void
7193 HoverEvent (int xPix, int yPix, int x, int y)
7194 {
7195         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7196         int r, f;
7197         if(!first.highlight) return;
7198         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7199         if(x == oldX && y == oldY) return; // only do something if we enter new square
7200         oldFromX = fromX; oldFromY = fromY;
7201         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7202           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7203             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7204         else if(oldX != x || oldY != y) {
7205           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7206           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7207             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7208           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7209             char buf[MSG_SIZ];
7210             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7211             SendToProgram(buf, &first);
7212           }
7213           oldX = x; oldY = y;
7214 //        SetHighlights(fromX, fromY, x, y);
7215         }
7216 }
7217
7218 void ReportClick(char *action, int x, int y)
7219 {
7220         char buf[MSG_SIZ]; // Inform engine of what user does
7221         int r, f;
7222         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7223           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7224         if(!first.highlight || gameMode == EditPosition) return;
7225         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7226         SendToProgram(buf, &first);
7227 }
7228
7229 void
7230 LeftClick (ClickType clickType, int xPix, int yPix)
7231 {
7232     int x, y;
7233     Boolean saveAnimate;
7234     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7235     char promoChoice = NULLCHAR;
7236     ChessSquare piece;
7237     static TimeMark lastClickTime, prevClickTime;
7238
7239     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7240
7241     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7242
7243     if (clickType == Press) ErrorPopDown();
7244     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7245
7246     x = EventToSquare(xPix, BOARD_WIDTH);
7247     y = EventToSquare(yPix, BOARD_HEIGHT);
7248     if (!flipView && y >= 0) {
7249         y = BOARD_HEIGHT - 1 - y;
7250     }
7251     if (flipView && x >= 0) {
7252         x = BOARD_WIDTH - 1 - x;
7253     }
7254
7255     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7256         defaultPromoChoice = promoSweep;
7257         promoSweep = EmptySquare;   // terminate sweep
7258         promoDefaultAltered = TRUE;
7259         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7260     }
7261
7262     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7263         if(clickType == Release) return; // ignore upclick of click-click destination
7264         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7265         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7266         if(gameInfo.holdingsWidth &&
7267                 (WhiteOnMove(currentMove)
7268                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7269                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7270             // click in right holdings, for determining promotion piece
7271             ChessSquare p = boards[currentMove][y][x];
7272             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7273             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7274             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7275                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7276                 fromX = fromY = -1;
7277                 return;
7278             }
7279         }
7280         DrawPosition(FALSE, boards[currentMove]);
7281         return;
7282     }
7283
7284     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7285     if(clickType == Press
7286             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7287               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7288               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7289         return;
7290
7291     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7292         // could be static click on premove from-square: abort premove
7293         gotPremove = 0;
7294         ClearPremoveHighlights();
7295     }
7296
7297     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7298         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7299
7300     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7301         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7302                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7303         defaultPromoChoice = DefaultPromoChoice(side);
7304     }
7305
7306     autoQueen = appData.alwaysPromoteToQueen;
7307
7308     if (fromX == -1) {
7309       int originalY = y;
7310       gatingPiece = EmptySquare;
7311       if (clickType != Press) {
7312         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7313             DragPieceEnd(xPix, yPix); dragging = 0;
7314             DrawPosition(FALSE, NULL);
7315         }
7316         return;
7317       }
7318       doubleClick = FALSE;
7319       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7320         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7321       }
7322       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7323       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7324          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7325          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7326             /* First square */
7327             if (OKToStartUserMove(fromX, fromY)) {
7328                 second = 0;
7329                 ReportClick("lift", x, y);
7330                 MarkTargetSquares(0);
7331                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7332                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7333                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7334                     promoSweep = defaultPromoChoice;
7335                     selectFlag = 0; lastX = xPix; lastY = yPix;
7336                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7337                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7338                 }
7339                 if (appData.highlightDragging) {
7340                     SetHighlights(fromX, fromY, -1, -1);
7341                 } else {
7342                     ClearHighlights();
7343                 }
7344             } else fromX = fromY = -1;
7345             return;
7346         }
7347     }
7348
7349     /* fromX != -1 */
7350     if (clickType == Press && gameMode != EditPosition) {
7351         ChessSquare fromP;
7352         ChessSquare toP;
7353         int frc;
7354
7355         // ignore off-board to clicks
7356         if(y < 0 || x < 0) return;
7357
7358         /* Check if clicking again on the same color piece */
7359         fromP = boards[currentMove][fromY][fromX];
7360         toP = boards[currentMove][y][x];
7361         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7362         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7363            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7364              WhitePawn <= toP && toP <= WhiteKing &&
7365              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7366              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7367             (BlackPawn <= fromP && fromP <= BlackKing &&
7368              BlackPawn <= toP && toP <= BlackKing &&
7369              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7370              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7371             /* Clicked again on same color piece -- changed his mind */
7372             second = (x == fromX && y == fromY);
7373             killX = killY = -1;
7374             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7375                 second = FALSE; // first double-click rather than scond click
7376                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7377             }
7378             promoDefaultAltered = FALSE;
7379             MarkTargetSquares(1);
7380            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7381             if (appData.highlightDragging) {
7382                 SetHighlights(x, y, -1, -1);
7383             } else {
7384                 ClearHighlights();
7385             }
7386             if (OKToStartUserMove(x, y)) {
7387                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7388                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7389                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7390                  gatingPiece = boards[currentMove][fromY][fromX];
7391                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7392                 fromX = x;
7393                 fromY = y; dragging = 1;
7394                 ReportClick("lift", x, y);
7395                 MarkTargetSquares(0);
7396                 DragPieceBegin(xPix, yPix, FALSE);
7397                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7398                     promoSweep = defaultPromoChoice;
7399                     selectFlag = 0; lastX = xPix; lastY = yPix;
7400                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7401                 }
7402             }
7403            }
7404            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7405            second = FALSE;
7406         }
7407         // ignore clicks on holdings
7408         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7409     }
7410
7411     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7412         DragPieceEnd(xPix, yPix); dragging = 0;
7413         if(clearFlag) {
7414             // a deferred attempt to click-click move an empty square on top of a piece
7415             boards[currentMove][y][x] = EmptySquare;
7416             ClearHighlights();
7417             DrawPosition(FALSE, boards[currentMove]);
7418             fromX = fromY = -1; clearFlag = 0;
7419             return;
7420         }
7421         if (appData.animateDragging) {
7422             /* Undo animation damage if any */
7423             DrawPosition(FALSE, NULL);
7424         }
7425         if (second || sweepSelecting) {
7426             /* Second up/down in same square; just abort move */
7427             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7428             second = sweepSelecting = 0;
7429             fromX = fromY = -1;
7430             gatingPiece = EmptySquare;
7431             MarkTargetSquares(1);
7432             ClearHighlights();
7433             gotPremove = 0;
7434             ClearPremoveHighlights();
7435         } else {
7436             /* First upclick in same square; start click-click mode */
7437             SetHighlights(x, y, -1, -1);
7438         }
7439         return;
7440     }
7441
7442     clearFlag = 0;
7443
7444     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7445         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7446         DisplayMessage(_("only marked squares are legal"),"");
7447         DrawPosition(TRUE, NULL);
7448         return; // ignore to-click
7449     }
7450
7451     /* we now have a different from- and (possibly off-board) to-square */
7452     /* Completed move */
7453     if(!sweepSelecting) {
7454         toX = x;
7455         toY = y;
7456     }
7457
7458     saveAnimate = appData.animate;
7459     if (clickType == Press) {
7460         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7461             // must be Edit Position mode with empty-square selected
7462             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7463             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7464             return;
7465         }
7466         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7467             return;
7468         }
7469         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7470             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7471         } else
7472         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7473         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7474           if(appData.sweepSelect) {
7475             ChessSquare piece = boards[currentMove][fromY][fromX];
7476             promoSweep = defaultPromoChoice;
7477             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7478             selectFlag = 0; lastX = xPix; lastY = yPix;
7479             Sweep(0); // Pawn that is going to promote: preview promotion piece
7480             sweepSelecting = 1;
7481             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7482             MarkTargetSquares(1);
7483           }
7484           return; // promo popup appears on up-click
7485         }
7486         /* Finish clickclick move */
7487         if (appData.animate || appData.highlightLastMove) {
7488             SetHighlights(fromX, fromY, toX, toY);
7489         } else {
7490             ClearHighlights();
7491         }
7492     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7493         sweepSelecting = 0;
7494         if (appData.animate || appData.highlightLastMove) {
7495             SetHighlights(fromX, fromY, toX, toY);
7496         } else {
7497             ClearHighlights();
7498         }
7499     } else {
7500 #if 0
7501 // [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
7502         /* Finish drag move */
7503         if (appData.highlightLastMove) {
7504             SetHighlights(fromX, fromY, toX, toY);
7505         } else {
7506             ClearHighlights();
7507         }
7508 #endif
7509         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7510           dragging *= 2;            // flag button-less dragging if we are dragging
7511           MarkTargetSquares(1);
7512           if(x == killX && y == killY) killX = killY = -1; else {
7513             killX = x; killY = y;     //remeber this square as intermediate
7514             ReportClick("put", x, y); // and inform engine
7515             ReportClick("lift", x, y);
7516             MarkTargetSquares(0);
7517             return;
7518           }
7519         }
7520         DragPieceEnd(xPix, yPix); dragging = 0;
7521         /* Don't animate move and drag both */
7522         appData.animate = FALSE;
7523     }
7524
7525     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7526     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7527         ChessSquare piece = boards[currentMove][fromY][fromX];
7528         if(gameMode == EditPosition && piece != EmptySquare &&
7529            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7530             int n;
7531
7532             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7533                 n = PieceToNumber(piece - (int)BlackPawn);
7534                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7535                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7536                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7537             } else
7538             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7539                 n = PieceToNumber(piece);
7540                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7541                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7542                 boards[currentMove][n][BOARD_WIDTH-2]++;
7543             }
7544             boards[currentMove][fromY][fromX] = EmptySquare;
7545         }
7546         ClearHighlights();
7547         fromX = fromY = -1;
7548         MarkTargetSquares(1);
7549         DrawPosition(TRUE, boards[currentMove]);
7550         return;
7551     }
7552
7553     // off-board moves should not be highlighted
7554     if(x < 0 || y < 0) ClearHighlights();
7555     else ReportClick("put", x, y);
7556
7557     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7558
7559     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7560         SetHighlights(fromX, fromY, toX, toY);
7561         MarkTargetSquares(1);
7562         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7563             // [HGM] super: promotion to captured piece selected from holdings
7564             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7565             promotionChoice = TRUE;
7566             // kludge follows to temporarily execute move on display, without promoting yet
7567             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7568             boards[currentMove][toY][toX] = p;
7569             DrawPosition(FALSE, boards[currentMove]);
7570             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7571             boards[currentMove][toY][toX] = q;
7572             DisplayMessage("Click in holdings to choose piece", "");
7573             return;
7574         }
7575         PromotionPopUp();
7576     } else {
7577         int oldMove = currentMove;
7578         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7579         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7580         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7581         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7582            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7583             DrawPosition(TRUE, boards[currentMove]);
7584         MarkTargetSquares(1);
7585         fromX = fromY = -1;
7586     }
7587     appData.animate = saveAnimate;
7588     if (appData.animate || appData.animateDragging) {
7589         /* Undo animation damage if needed */
7590         DrawPosition(FALSE, NULL);
7591     }
7592 }
7593
7594 int
7595 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7596 {   // front-end-free part taken out of PieceMenuPopup
7597     int whichMenu; int xSqr, ySqr;
7598
7599     if(seekGraphUp) { // [HGM] seekgraph
7600         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7601         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7602         return -2;
7603     }
7604
7605     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7606          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7607         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7608         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7609         if(action == Press)   {
7610             originalFlip = flipView;
7611             flipView = !flipView; // temporarily flip board to see game from partners perspective
7612             DrawPosition(TRUE, partnerBoard);
7613             DisplayMessage(partnerStatus, "");
7614             partnerUp = TRUE;
7615         } else if(action == Release) {
7616             flipView = originalFlip;
7617             DrawPosition(TRUE, boards[currentMove]);
7618             partnerUp = FALSE;
7619         }
7620         return -2;
7621     }
7622
7623     xSqr = EventToSquare(x, BOARD_WIDTH);
7624     ySqr = EventToSquare(y, BOARD_HEIGHT);
7625     if (action == Release) {
7626         if(pieceSweep != EmptySquare) {
7627             EditPositionMenuEvent(pieceSweep, toX, toY);
7628             pieceSweep = EmptySquare;
7629         } else UnLoadPV(); // [HGM] pv
7630     }
7631     if (action != Press) return -2; // return code to be ignored
7632     switch (gameMode) {
7633       case IcsExamining:
7634         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7635       case EditPosition:
7636         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7637         if (xSqr < 0 || ySqr < 0) return -1;
7638         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7639         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7640         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7641         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7642         NextPiece(0);
7643         return 2; // grab
7644       case IcsObserving:
7645         if(!appData.icsEngineAnalyze) return -1;
7646       case IcsPlayingWhite:
7647       case IcsPlayingBlack:
7648         if(!appData.zippyPlay) goto noZip;
7649       case AnalyzeMode:
7650       case AnalyzeFile:
7651       case MachinePlaysWhite:
7652       case MachinePlaysBlack:
7653       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7654         if (!appData.dropMenu) {
7655           LoadPV(x, y);
7656           return 2; // flag front-end to grab mouse events
7657         }
7658         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7659            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7660       case EditGame:
7661       noZip:
7662         if (xSqr < 0 || ySqr < 0) return -1;
7663         if (!appData.dropMenu || appData.testLegality &&
7664             gameInfo.variant != VariantBughouse &&
7665             gameInfo.variant != VariantCrazyhouse) return -1;
7666         whichMenu = 1; // drop menu
7667         break;
7668       default:
7669         return -1;
7670     }
7671
7672     if (((*fromX = xSqr) < 0) ||
7673         ((*fromY = ySqr) < 0)) {
7674         *fromX = *fromY = -1;
7675         return -1;
7676     }
7677     if (flipView)
7678       *fromX = BOARD_WIDTH - 1 - *fromX;
7679     else
7680       *fromY = BOARD_HEIGHT - 1 - *fromY;
7681
7682     return whichMenu;
7683 }
7684
7685 void
7686 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7687 {
7688 //    char * hint = lastHint;
7689     FrontEndProgramStats stats;
7690
7691     stats.which = cps == &first ? 0 : 1;
7692     stats.depth = cpstats->depth;
7693     stats.nodes = cpstats->nodes;
7694     stats.score = cpstats->score;
7695     stats.time = cpstats->time;
7696     stats.pv = cpstats->movelist;
7697     stats.hint = lastHint;
7698     stats.an_move_index = 0;
7699     stats.an_move_count = 0;
7700
7701     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7702         stats.hint = cpstats->move_name;
7703         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7704         stats.an_move_count = cpstats->nr_moves;
7705     }
7706
7707     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
7708
7709     SetProgramStats( &stats );
7710 }
7711
7712 void
7713 ClearEngineOutputPane (int which)
7714 {
7715     static FrontEndProgramStats dummyStats;
7716     dummyStats.which = which;
7717     dummyStats.pv = "#";
7718     SetProgramStats( &dummyStats );
7719 }
7720
7721 #define MAXPLAYERS 500
7722
7723 char *
7724 TourneyStandings (int display)
7725 {
7726     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7727     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7728     char result, *p, *names[MAXPLAYERS];
7729
7730     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7731         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7732     names[0] = p = strdup(appData.participants);
7733     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7734
7735     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7736
7737     while(result = appData.results[nr]) {
7738         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7739         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7740         wScore = bScore = 0;
7741         switch(result) {
7742           case '+': wScore = 2; break;
7743           case '-': bScore = 2; break;
7744           case '=': wScore = bScore = 1; break;
7745           case ' ':
7746           case '*': return strdup("busy"); // tourney not finished
7747         }
7748         score[w] += wScore;
7749         score[b] += bScore;
7750         games[w]++;
7751         games[b]++;
7752         nr++;
7753     }
7754     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7755     for(w=0; w<nPlayers; w++) {
7756         bScore = -1;
7757         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7758         ranking[w] = b; points[w] = bScore; score[b] = -2;
7759     }
7760     p = malloc(nPlayers*34+1);
7761     for(w=0; w<nPlayers && w<display; w++)
7762         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7763     free(names[0]);
7764     return p;
7765 }
7766
7767 void
7768 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7769 {       // count all piece types
7770         int p, f, r;
7771         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7772         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7773         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7774                 p = board[r][f];
7775                 pCnt[p]++;
7776                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7777                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7778                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7779                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7780                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7781                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7782         }
7783 }
7784
7785 int
7786 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7787 {
7788         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7789         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7790
7791         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7792         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7793         if(myPawns == 2 && nMine == 3) // KPP
7794             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7795         if(myPawns == 1 && nMine == 2) // KP
7796             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7797         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7798             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7799         if(myPawns) return FALSE;
7800         if(pCnt[WhiteRook+side])
7801             return pCnt[BlackRook-side] ||
7802                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7803                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7804                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7805         if(pCnt[WhiteCannon+side]) {
7806             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7807             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7808         }
7809         if(pCnt[WhiteKnight+side])
7810             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7811         return FALSE;
7812 }
7813
7814 int
7815 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7816 {
7817         VariantClass v = gameInfo.variant;
7818
7819         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7820         if(v == VariantShatranj) return TRUE; // always winnable through baring
7821         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7822         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7823
7824         if(v == VariantXiangqi) {
7825                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7826
7827                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7828                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7829                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7830                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7831                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7832                 if(stale) // we have at least one last-rank P plus perhaps C
7833                     return majors // KPKX
7834                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7835                 else // KCA*E*
7836                     return pCnt[WhiteFerz+side] // KCAK
7837                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7838                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7839                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7840
7841         } else if(v == VariantKnightmate) {
7842                 if(nMine == 1) return FALSE;
7843                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7844         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7845                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7846
7847                 if(nMine == 1) return FALSE; // bare King
7848                 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
7849                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7850                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7851                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7852                 if(pCnt[WhiteKnight+side])
7853                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7854                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7855                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7856                 if(nBishops)
7857                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7858                 if(pCnt[WhiteAlfil+side])
7859                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7860                 if(pCnt[WhiteWazir+side])
7861                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7862         }
7863
7864         return TRUE;
7865 }
7866
7867 int
7868 CompareWithRights (Board b1, Board b2)
7869 {
7870     int rights = 0;
7871     if(!CompareBoards(b1, b2)) return FALSE;
7872     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7873     /* compare castling rights */
7874     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7875            rights++; /* King lost rights, while rook still had them */
7876     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7877         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7878            rights++; /* but at least one rook lost them */
7879     }
7880     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7881            rights++;
7882     if( b1[CASTLING][5] != NoRights ) {
7883         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7884            rights++;
7885     }
7886     return rights == 0;
7887 }
7888
7889 int
7890 Adjudicate (ChessProgramState *cps)
7891 {       // [HGM] some adjudications useful with buggy engines
7892         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7893         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7894         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7895         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7896         int k, drop, count = 0; static int bare = 1;
7897         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7898         Boolean canAdjudicate = !appData.icsActive;
7899
7900         // most tests only when we understand the game, i.e. legality-checking on
7901             if( appData.testLegality )
7902             {   /* [HGM] Some more adjudications for obstinate engines */
7903                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7904                 static int moveCount = 6;
7905                 ChessMove result;
7906                 char *reason = NULL;
7907
7908                 /* Count what is on board. */
7909                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7910
7911                 /* Some material-based adjudications that have to be made before stalemate test */
7912                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7913                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7914                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7915                      if(canAdjudicate && appData.checkMates) {
7916                          if(engineOpponent)
7917                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7918                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7919                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7920                          return 1;
7921                      }
7922                 }
7923
7924                 /* Bare King in Shatranj (loses) or Losers (wins) */
7925                 if( nrW == 1 || nrB == 1) {
7926                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7927                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7928                      if(canAdjudicate && appData.checkMates) {
7929                          if(engineOpponent)
7930                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7931                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7932                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7933                          return 1;
7934                      }
7935                   } else
7936                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7937                   {    /* bare King */
7938                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7939                         if(canAdjudicate && appData.checkMates) {
7940                             /* but only adjudicate if adjudication enabled */
7941                             if(engineOpponent)
7942                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7943                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7944                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7945                             return 1;
7946                         }
7947                   }
7948                 } else bare = 1;
7949
7950
7951             // don't wait for engine to announce game end if we can judge ourselves
7952             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7953               case MT_CHECK:
7954                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7955                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7956                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7957                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7958                             checkCnt++;
7959                         if(checkCnt >= 2) {
7960                             reason = "Xboard adjudication: 3rd check";
7961                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7962                             break;
7963                         }
7964                     }
7965                 }
7966               case MT_NONE:
7967               default:
7968                 break;
7969               case MT_STALEMATE:
7970               case MT_STAINMATE:
7971                 reason = "Xboard adjudication: Stalemate";
7972                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7973                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7974                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7975                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7976                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7977                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7978                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7979                                                                         EP_CHECKMATE : EP_WINS);
7980                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7981                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7982                 }
7983                 break;
7984               case MT_CHECKMATE:
7985                 reason = "Xboard adjudication: Checkmate";
7986                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7987                 if(gameInfo.variant == VariantShogi) {
7988                     if(forwardMostMove > backwardMostMove
7989                        && moveList[forwardMostMove-1][1] == '@'
7990                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7991                         reason = "XBoard adjudication: pawn-drop mate";
7992                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7993                     }
7994                 }
7995                 break;
7996             }
7997
7998                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7999                     case EP_STALEMATE:
8000                         result = GameIsDrawn; break;
8001                     case EP_CHECKMATE:
8002                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8003                     case EP_WINS:
8004                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8005                     default:
8006                         result = EndOfFile;
8007                 }
8008                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8009                     if(engineOpponent)
8010                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8011                     GameEnds( result, reason, GE_XBOARD );
8012                     return 1;
8013                 }
8014
8015                 /* Next absolutely insufficient mating material. */
8016                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8017                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8018                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8019
8020                      /* always flag draws, for judging claims */
8021                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8022
8023                      if(canAdjudicate && appData.materialDraws) {
8024                          /* but only adjudicate them if adjudication enabled */
8025                          if(engineOpponent) {
8026                            SendToProgram("force\n", engineOpponent); // suppress reply
8027                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8028                          }
8029                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8030                          return 1;
8031                      }
8032                 }
8033
8034                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8035                 if(gameInfo.variant == VariantXiangqi ?
8036                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8037                  : nrW + nrB == 4 &&
8038                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8039                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8040                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8041                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8042                    ) ) {
8043                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8044                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8045                           if(engineOpponent) {
8046                             SendToProgram("force\n", engineOpponent); // suppress reply
8047                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8048                           }
8049                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8050                           return 1;
8051                      }
8052                 } else moveCount = 6;
8053             }
8054
8055         // Repetition draws and 50-move rule can be applied independently of legality testing
8056
8057                 /* Check for rep-draws */
8058                 count = 0;
8059                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8060                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8061                 for(k = forwardMostMove-2;
8062                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8063                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8064                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8065                     k-=2)
8066                 {   int rights=0;
8067                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8068                         /* compare castling rights */
8069                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8070                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8071                                 rights++; /* King lost rights, while rook still had them */
8072                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8073                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8074                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8075                                    rights++; /* but at least one rook lost them */
8076                         }
8077                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8078                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8079                                 rights++;
8080                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8081                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8082                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8083                                    rights++;
8084                         }
8085                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8086                             && appData.drawRepeats > 1) {
8087                              /* adjudicate after user-specified nr of repeats */
8088                              int result = GameIsDrawn;
8089                              char *details = "XBoard adjudication: repetition draw";
8090                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8091                                 // [HGM] xiangqi: check for forbidden perpetuals
8092                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8093                                 for(m=forwardMostMove; m>k; m-=2) {
8094                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8095                                         ourPerpetual = 0; // the current mover did not always check
8096                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8097                                         hisPerpetual = 0; // the opponent did not always check
8098                                 }
8099                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8100                                                                         ourPerpetual, hisPerpetual);
8101                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8102                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8103                                     details = "Xboard adjudication: perpetual checking";
8104                                 } else
8105                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8106                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8107                                 } else
8108                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8109                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8110                                         result = BlackWins;
8111                                         details = "Xboard adjudication: repetition";
8112                                     }
8113                                 } else // it must be XQ
8114                                 // Now check for perpetual chases
8115                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8116                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8117                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8118                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8119                                         static char resdet[MSG_SIZ];
8120                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8121                                         details = resdet;
8122                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8123                                     } else
8124                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8125                                         break; // Abort repetition-checking loop.
8126                                 }
8127                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8128                              }
8129                              if(engineOpponent) {
8130                                SendToProgram("force\n", engineOpponent); // suppress reply
8131                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8132                              }
8133                              GameEnds( result, details, GE_XBOARD );
8134                              return 1;
8135                         }
8136                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8137                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8138                     }
8139                 }
8140
8141                 /* Now we test for 50-move draws. Determine ply count */
8142                 count = forwardMostMove;
8143                 /* look for last irreversble move */
8144                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8145                     count--;
8146                 /* if we hit starting position, add initial plies */
8147                 if( count == backwardMostMove )
8148                     count -= initialRulePlies;
8149                 count = forwardMostMove - count;
8150                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8151                         // adjust reversible move counter for checks in Xiangqi
8152                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8153                         if(i < backwardMostMove) i = backwardMostMove;
8154                         while(i <= forwardMostMove) {
8155                                 lastCheck = inCheck; // check evasion does not count
8156                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8157                                 if(inCheck || lastCheck) count--; // check does not count
8158                                 i++;
8159                         }
8160                 }
8161                 if( count >= 100)
8162                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8163                          /* this is used to judge if draw claims are legal */
8164                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8165                          if(engineOpponent) {
8166                            SendToProgram("force\n", engineOpponent); // suppress reply
8167                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8168                          }
8169                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8170                          return 1;
8171                 }
8172
8173                 /* if draw offer is pending, treat it as a draw claim
8174                  * when draw condition present, to allow engines a way to
8175                  * claim draws before making their move to avoid a race
8176                  * condition occurring after their move
8177                  */
8178                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8179                          char *p = NULL;
8180                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8181                              p = "Draw claim: 50-move rule";
8182                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8183                              p = "Draw claim: 3-fold repetition";
8184                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8185                              p = "Draw claim: insufficient mating material";
8186                          if( p != NULL && canAdjudicate) {
8187                              if(engineOpponent) {
8188                                SendToProgram("force\n", engineOpponent); // suppress reply
8189                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8190                              }
8191                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8192                              return 1;
8193                          }
8194                 }
8195
8196                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8197                     if(engineOpponent) {
8198                       SendToProgram("force\n", engineOpponent); // suppress reply
8199                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8200                     }
8201                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8202                     return 1;
8203                 }
8204         return 0;
8205 }
8206
8207 char *
8208 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8209 {   // [HGM] book: this routine intercepts moves to simulate book replies
8210     char *bookHit = NULL;
8211
8212     //first determine if the incoming move brings opponent into his book
8213     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8214         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8215     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8216     if(bookHit != NULL && !cps->bookSuspend) {
8217         // make sure opponent is not going to reply after receiving move to book position
8218         SendToProgram("force\n", cps);
8219         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8220     }
8221     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8222     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8223     // now arrange restart after book miss
8224     if(bookHit) {
8225         // after a book hit we never send 'go', and the code after the call to this routine
8226         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8227         char buf[MSG_SIZ], *move = bookHit;
8228         if(cps->useSAN) {
8229             int fromX, fromY, toX, toY;
8230             char promoChar;
8231             ChessMove moveType;
8232             move = buf + 30;
8233             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8234                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8235                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8236                                     PosFlags(forwardMostMove),
8237                                     fromY, fromX, toY, toX, promoChar, move);
8238             } else {
8239                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8240                 bookHit = NULL;
8241             }
8242         }
8243         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8244         SendToProgram(buf, cps);
8245         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8246     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8247         SendToProgram("go\n", cps);
8248         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8249     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8250         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8251             SendToProgram("go\n", cps);
8252         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8253     }
8254     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8255 }
8256
8257 int
8258 LoadError (char *errmess, ChessProgramState *cps)
8259 {   // unloads engine and switches back to -ncp mode if it was first
8260     if(cps->initDone) return FALSE;
8261     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8262     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8263     cps->pr = NoProc;
8264     if(cps == &first) {
8265         appData.noChessProgram = TRUE;
8266         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8267         gameMode = BeginningOfGame; ModeHighlight();
8268         SetNCPMode();
8269     }
8270     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8271     DisplayMessage("", ""); // erase waiting message
8272     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8273     return TRUE;
8274 }
8275
8276 char *savedMessage;
8277 ChessProgramState *savedState;
8278 void
8279 DeferredBookMove (void)
8280 {
8281         if(savedState->lastPing != savedState->lastPong)
8282                     ScheduleDelayedEvent(DeferredBookMove, 10);
8283         else
8284         HandleMachineMove(savedMessage, savedState);
8285 }
8286
8287 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8288 static ChessProgramState *stalledEngine;
8289 static char stashedInputMove[MSG_SIZ];
8290
8291 void
8292 HandleMachineMove (char *message, ChessProgramState *cps)
8293 {
8294     static char firstLeg[20];
8295     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8296     char realname[MSG_SIZ];
8297     int fromX, fromY, toX, toY;
8298     ChessMove moveType;
8299     char promoChar, roar;
8300     char *p, *pv=buf1;
8301     int machineWhite, oldError;
8302     char *bookHit;
8303
8304     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8305         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8306         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8307             DisplayError(_("Invalid pairing from pairing engine"), 0);
8308             return;
8309         }
8310         pairingReceived = 1;
8311         NextMatchGame();
8312         return; // Skim the pairing messages here.
8313     }
8314
8315     oldError = cps->userError; cps->userError = 0;
8316
8317 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8318     /*
8319      * Kludge to ignore BEL characters
8320      */
8321     while (*message == '\007') message++;
8322
8323     /*
8324      * [HGM] engine debug message: ignore lines starting with '#' character
8325      */
8326     if(cps->debug && *message == '#') return;
8327
8328     /*
8329      * Look for book output
8330      */
8331     if (cps == &first && bookRequested) {
8332         if (message[0] == '\t' || message[0] == ' ') {
8333             /* Part of the book output is here; append it */
8334             strcat(bookOutput, message);
8335             strcat(bookOutput, "  \n");
8336             return;
8337         } else if (bookOutput[0] != NULLCHAR) {
8338             /* All of book output has arrived; display it */
8339             char *p = bookOutput;
8340             while (*p != NULLCHAR) {
8341                 if (*p == '\t') *p = ' ';
8342                 p++;
8343             }
8344             DisplayInformation(bookOutput);
8345             bookRequested = FALSE;
8346             /* Fall through to parse the current output */
8347         }
8348     }
8349
8350     /*
8351      * Look for machine move.
8352      */
8353     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8354         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8355     {
8356         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8357             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8358             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8359             stalledEngine = cps;
8360             if(appData.ponderNextMove) { // bring opponent out of ponder
8361                 if(gameMode == TwoMachinesPlay) {
8362                     if(cps->other->pause)
8363                         PauseEngine(cps->other);
8364                     else
8365                         SendToProgram("easy\n", cps->other);
8366                 }
8367             }
8368             StopClocks();
8369             return;
8370         }
8371
8372         /* This method is only useful on engines that support ping */
8373         if (cps->lastPing != cps->lastPong) {
8374           if (gameMode == BeginningOfGame) {
8375             /* Extra move from before last new; ignore */
8376             if (appData.debugMode) {
8377                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8378             }
8379           } else {
8380             if (appData.debugMode) {
8381                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8382                         cps->which, gameMode);
8383             }
8384
8385             SendToProgram("undo\n", cps);
8386           }
8387           return;
8388         }
8389
8390         switch (gameMode) {
8391           case BeginningOfGame:
8392             /* Extra move from before last reset; ignore */
8393             if (appData.debugMode) {
8394                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8395             }
8396             return;
8397
8398           case EndOfGame:
8399           case IcsIdle:
8400           default:
8401             /* Extra move after we tried to stop.  The mode test is
8402                not a reliable way of detecting this problem, but it's
8403                the best we can do on engines that don't support ping.
8404             */
8405             if (appData.debugMode) {
8406                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8407                         cps->which, gameMode);
8408             }
8409             SendToProgram("undo\n", cps);
8410             return;
8411
8412           case MachinePlaysWhite:
8413           case IcsPlayingWhite:
8414             machineWhite = TRUE;
8415             break;
8416
8417           case MachinePlaysBlack:
8418           case IcsPlayingBlack:
8419             machineWhite = FALSE;
8420             break;
8421
8422           case TwoMachinesPlay:
8423             machineWhite = (cps->twoMachinesColor[0] == 'w');
8424             break;
8425         }
8426         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8427             if (appData.debugMode) {
8428                 fprintf(debugFP,
8429                         "Ignoring move out of turn by %s, gameMode %d"
8430                         ", forwardMost %d\n",
8431                         cps->which, gameMode, forwardMostMove);
8432             }
8433             return;
8434         }
8435
8436         if(cps->alphaRank) AlphaRank(machineMove, 4);
8437
8438         // [HGM] lion: (some very limited) support for Alien protocol
8439         killX = killY = -1;
8440         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8441             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8442             return;
8443         } else if(firstLeg[0]) { // there was a previous leg;
8444             // only support case where same piece makes two step (and don't even test that!)
8445             char buf[20], *p = machineMove+1, *q = buf+1, f;
8446             safeStrCpy(buf, machineMove, 20);
8447             while(isdigit(*q)) q++; // find start of to-square
8448             safeStrCpy(machineMove, firstLeg, 20);
8449             while(isdigit(*p)) p++;
8450             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8451             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8452             firstLeg[0] = NULLCHAR;
8453         }
8454
8455         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8456                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8457             /* Machine move could not be parsed; ignore it. */
8458           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8459                     machineMove, _(cps->which));
8460             DisplayMoveError(buf1);
8461             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8462                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8463             if (gameMode == TwoMachinesPlay) {
8464               GameEnds(machineWhite ? BlackWins : WhiteWins,
8465                        buf1, GE_XBOARD);
8466             }
8467             return;
8468         }
8469
8470         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8471         /* So we have to redo legality test with true e.p. status here,  */
8472         /* to make sure an illegal e.p. capture does not slip through,   */
8473         /* to cause a forfeit on a justified illegal-move complaint      */
8474         /* of the opponent.                                              */
8475         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8476            ChessMove moveType;
8477            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8478                              fromY, fromX, toY, toX, promoChar);
8479             if(moveType == IllegalMove) {
8480               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8481                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8482                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8483                            buf1, GE_XBOARD);
8484                 return;
8485            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8486            /* [HGM] Kludge to handle engines that send FRC-style castling
8487               when they shouldn't (like TSCP-Gothic) */
8488            switch(moveType) {
8489              case WhiteASideCastleFR:
8490              case BlackASideCastleFR:
8491                toX+=2;
8492                currentMoveString[2]++;
8493                break;
8494              case WhiteHSideCastleFR:
8495              case BlackHSideCastleFR:
8496                toX--;
8497                currentMoveString[2]--;
8498                break;
8499              default: ; // nothing to do, but suppresses warning of pedantic compilers
8500            }
8501         }
8502         hintRequested = FALSE;
8503         lastHint[0] = NULLCHAR;
8504         bookRequested = FALSE;
8505         /* Program may be pondering now */
8506         cps->maybeThinking = TRUE;
8507         if (cps->sendTime == 2) cps->sendTime = 1;
8508         if (cps->offeredDraw) cps->offeredDraw--;
8509
8510         /* [AS] Save move info*/
8511         pvInfoList[ forwardMostMove ].score = programStats.score;
8512         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8513         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8514
8515         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8516
8517         /* Test suites abort the 'game' after one move */
8518         if(*appData.finger) {
8519            static FILE *f;
8520            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8521            if(!f) f = fopen(appData.finger, "w");
8522            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8523            else { DisplayFatalError("Bad output file", errno, 0); return; }
8524            free(fen);
8525            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8526         }
8527
8528         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8529         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8530             int count = 0;
8531
8532             while( count < adjudicateLossPlies ) {
8533                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8534
8535                 if( count & 1 ) {
8536                     score = -score; /* Flip score for winning side */
8537                 }
8538
8539                 if( score > adjudicateLossThreshold ) {
8540                     break;
8541                 }
8542
8543                 count++;
8544             }
8545
8546             if( count >= adjudicateLossPlies ) {
8547                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8548
8549                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8550                     "Xboard adjudication",
8551                     GE_XBOARD );
8552
8553                 return;
8554             }
8555         }
8556
8557         if(Adjudicate(cps)) {
8558             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8559             return; // [HGM] adjudicate: for all automatic game ends
8560         }
8561
8562 #if ZIPPY
8563         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8564             first.initDone) {
8565           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8566                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8567                 SendToICS("draw ");
8568                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8569           }
8570           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8571           ics_user_moved = 1;
8572           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8573                 char buf[3*MSG_SIZ];
8574
8575                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8576                         programStats.score / 100.,
8577                         programStats.depth,
8578                         programStats.time / 100.,
8579                         (unsigned int)programStats.nodes,
8580                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8581                         programStats.movelist);
8582                 SendToICS(buf);
8583 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8584           }
8585         }
8586 #endif
8587
8588         /* [AS] Clear stats for next move */
8589         ClearProgramStats();
8590         thinkOutput[0] = NULLCHAR;
8591         hiddenThinkOutputState = 0;
8592
8593         bookHit = NULL;
8594         if (gameMode == TwoMachinesPlay) {
8595             /* [HGM] relaying draw offers moved to after reception of move */
8596             /* and interpreting offer as claim if it brings draw condition */
8597             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8598                 SendToProgram("draw\n", cps->other);
8599             }
8600             if (cps->other->sendTime) {
8601                 SendTimeRemaining(cps->other,
8602                                   cps->other->twoMachinesColor[0] == 'w');
8603             }
8604             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8605             if (firstMove && !bookHit) {
8606                 firstMove = FALSE;
8607                 if (cps->other->useColors) {
8608                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8609                 }
8610                 SendToProgram("go\n", cps->other);
8611             }
8612             cps->other->maybeThinking = TRUE;
8613         }
8614
8615         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8616
8617         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8618
8619         if (!pausing && appData.ringBellAfterMoves) {
8620             if(!roar) RingBell();
8621         }
8622
8623         /*
8624          * Reenable menu items that were disabled while
8625          * machine was thinking
8626          */
8627         if (gameMode != TwoMachinesPlay)
8628             SetUserThinkingEnables();
8629
8630         // [HGM] book: after book hit opponent has received move and is now in force mode
8631         // force the book reply into it, and then fake that it outputted this move by jumping
8632         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8633         if(bookHit) {
8634                 static char bookMove[MSG_SIZ]; // a bit generous?
8635
8636                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8637                 strcat(bookMove, bookHit);
8638                 message = bookMove;
8639                 cps = cps->other;
8640                 programStats.nodes = programStats.depth = programStats.time =
8641                 programStats.score = programStats.got_only_move = 0;
8642                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8643
8644                 if(cps->lastPing != cps->lastPong) {
8645                     savedMessage = message; // args for deferred call
8646                     savedState = cps;
8647                     ScheduleDelayedEvent(DeferredBookMove, 10);
8648                     return;
8649                 }
8650                 goto FakeBookMove;
8651         }
8652
8653         return;
8654     }
8655
8656     /* Set special modes for chess engines.  Later something general
8657      *  could be added here; for now there is just one kludge feature,
8658      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8659      *  when "xboard" is given as an interactive command.
8660      */
8661     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8662         cps->useSigint = FALSE;
8663         cps->useSigterm = FALSE;
8664     }
8665     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8666       ParseFeatures(message+8, cps);
8667       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8668     }
8669
8670     if (!strncmp(message, "setup ", 6) && 
8671         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8672                                         ) { // [HGM] allow first engine to define opening position
8673       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8674       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8675       *buf = NULLCHAR;
8676       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8677       if(startedFromSetupPosition) return;
8678       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8679       if(dummy >= 3) {
8680         while(message[s] && message[s++] != ' ');
8681         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8682            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8683             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8684             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8685           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8686           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8687         }
8688       }
8689       ParseFEN(boards[0], &dummy, message+s, FALSE);
8690       DrawPosition(TRUE, boards[0]);
8691       startedFromSetupPosition = TRUE;
8692       return;
8693     }
8694     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8695      * want this, I was asked to put it in, and obliged.
8696      */
8697     if (!strncmp(message, "setboard ", 9)) {
8698         Board initial_position;
8699
8700         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8701
8702         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8703             DisplayError(_("Bad FEN received from engine"), 0);
8704             return ;
8705         } else {
8706            Reset(TRUE, FALSE);
8707            CopyBoard(boards[0], initial_position);
8708            initialRulePlies = FENrulePlies;
8709            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8710            else gameMode = MachinePlaysBlack;
8711            DrawPosition(FALSE, boards[currentMove]);
8712         }
8713         return;
8714     }
8715
8716     /*
8717      * Look for communication commands
8718      */
8719     if (!strncmp(message, "telluser ", 9)) {
8720         if(message[9] == '\\' && message[10] == '\\')
8721             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8722         PlayTellSound();
8723         DisplayNote(message + 9);
8724         return;
8725     }
8726     if (!strncmp(message, "tellusererror ", 14)) {
8727         cps->userError = 1;
8728         if(message[14] == '\\' && message[15] == '\\')
8729             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8730         PlayTellSound();
8731         DisplayError(message + 14, 0);
8732         return;
8733     }
8734     if (!strncmp(message, "tellopponent ", 13)) {
8735       if (appData.icsActive) {
8736         if (loggedOn) {
8737           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8738           SendToICS(buf1);
8739         }
8740       } else {
8741         DisplayNote(message + 13);
8742       }
8743       return;
8744     }
8745     if (!strncmp(message, "tellothers ", 11)) {
8746       if (appData.icsActive) {
8747         if (loggedOn) {
8748           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8749           SendToICS(buf1);
8750         }
8751       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8752       return;
8753     }
8754     if (!strncmp(message, "tellall ", 8)) {
8755       if (appData.icsActive) {
8756         if (loggedOn) {
8757           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8758           SendToICS(buf1);
8759         }
8760       } else {
8761         DisplayNote(message + 8);
8762       }
8763       return;
8764     }
8765     if (strncmp(message, "warning", 7) == 0) {
8766         /* Undocumented feature, use tellusererror in new code */
8767         DisplayError(message, 0);
8768         return;
8769     }
8770     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8771         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8772         strcat(realname, " query");
8773         AskQuestion(realname, buf2, buf1, cps->pr);
8774         return;
8775     }
8776     /* Commands from the engine directly to ICS.  We don't allow these to be
8777      *  sent until we are logged on. Crafty kibitzes have been known to
8778      *  interfere with the login process.
8779      */
8780     if (loggedOn) {
8781         if (!strncmp(message, "tellics ", 8)) {
8782             SendToICS(message + 8);
8783             SendToICS("\n");
8784             return;
8785         }
8786         if (!strncmp(message, "tellicsnoalias ", 15)) {
8787             SendToICS(ics_prefix);
8788             SendToICS(message + 15);
8789             SendToICS("\n");
8790             return;
8791         }
8792         /* The following are for backward compatibility only */
8793         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8794             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8795             SendToICS(ics_prefix);
8796             SendToICS(message);
8797             SendToICS("\n");
8798             return;
8799         }
8800     }
8801     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8802         return;
8803     }
8804     if(!strncmp(message, "highlight ", 10)) {
8805         if(appData.testLegality && appData.markers) return;
8806         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8807         return;
8808     }
8809     if(!strncmp(message, "click ", 6)) {
8810         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8811         if(appData.testLegality || !appData.oneClick) return;
8812         sscanf(message+6, "%c%d%c", &f, &y, &c);
8813         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8814         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8815         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8816         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8817         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8818         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8819             LeftClick(Release, lastLeftX, lastLeftY);
8820         controlKey  = (c == ',');
8821         LeftClick(Press, x, y);
8822         LeftClick(Release, x, y);
8823         first.highlight = f;
8824         return;
8825     }
8826     /*
8827      * If the move is illegal, cancel it and redraw the board.
8828      * Also deal with other error cases.  Matching is rather loose
8829      * here to accommodate engines written before the spec.
8830      */
8831     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8832         strncmp(message, "Error", 5) == 0) {
8833         if (StrStr(message, "name") ||
8834             StrStr(message, "rating") || StrStr(message, "?") ||
8835             StrStr(message, "result") || StrStr(message, "board") ||
8836             StrStr(message, "bk") || StrStr(message, "computer") ||
8837             StrStr(message, "variant") || StrStr(message, "hint") ||
8838             StrStr(message, "random") || StrStr(message, "depth") ||
8839             StrStr(message, "accepted")) {
8840             return;
8841         }
8842         if (StrStr(message, "protover")) {
8843           /* Program is responding to input, so it's apparently done
8844              initializing, and this error message indicates it is
8845              protocol version 1.  So we don't need to wait any longer
8846              for it to initialize and send feature commands. */
8847           FeatureDone(cps, 1);
8848           cps->protocolVersion = 1;
8849           return;
8850         }
8851         cps->maybeThinking = FALSE;
8852
8853         if (StrStr(message, "draw")) {
8854             /* Program doesn't have "draw" command */
8855             cps->sendDrawOffers = 0;
8856             return;
8857         }
8858         if (cps->sendTime != 1 &&
8859             (StrStr(message, "time") || StrStr(message, "otim"))) {
8860           /* Program apparently doesn't have "time" or "otim" command */
8861           cps->sendTime = 0;
8862           return;
8863         }
8864         if (StrStr(message, "analyze")) {
8865             cps->analysisSupport = FALSE;
8866             cps->analyzing = FALSE;
8867 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8868             EditGameEvent(); // [HGM] try to preserve loaded game
8869             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8870             DisplayError(buf2, 0);
8871             return;
8872         }
8873         if (StrStr(message, "(no matching move)st")) {
8874           /* Special kludge for GNU Chess 4 only */
8875           cps->stKludge = TRUE;
8876           SendTimeControl(cps, movesPerSession, timeControl,
8877                           timeIncrement, appData.searchDepth,
8878                           searchTime);
8879           return;
8880         }
8881         if (StrStr(message, "(no matching move)sd")) {
8882           /* Special kludge for GNU Chess 4 only */
8883           cps->sdKludge = TRUE;
8884           SendTimeControl(cps, movesPerSession, timeControl,
8885                           timeIncrement, appData.searchDepth,
8886                           searchTime);
8887           return;
8888         }
8889         if (!StrStr(message, "llegal")) {
8890             return;
8891         }
8892         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8893             gameMode == IcsIdle) return;
8894         if (forwardMostMove <= backwardMostMove) return;
8895         if (pausing) PauseEvent();
8896       if(appData.forceIllegal) {
8897             // [HGM] illegal: machine refused move; force position after move into it
8898           SendToProgram("force\n", cps);
8899           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8900                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8901                 // when black is to move, while there might be nothing on a2 or black
8902                 // might already have the move. So send the board as if white has the move.
8903                 // But first we must change the stm of the engine, as it refused the last move
8904                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8905                 if(WhiteOnMove(forwardMostMove)) {
8906                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8907                     SendBoard(cps, forwardMostMove); // kludgeless board
8908                 } else {
8909                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8910                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8911                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8912                 }
8913           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8914             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8915                  gameMode == TwoMachinesPlay)
8916               SendToProgram("go\n", cps);
8917             return;
8918       } else
8919         if (gameMode == PlayFromGameFile) {
8920             /* Stop reading this game file */
8921             gameMode = EditGame;
8922             ModeHighlight();
8923         }
8924         /* [HGM] illegal-move claim should forfeit game when Xboard */
8925         /* only passes fully legal moves                            */
8926         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8927             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8928                                 "False illegal-move claim", GE_XBOARD );
8929             return; // do not take back move we tested as valid
8930         }
8931         currentMove = forwardMostMove-1;
8932         DisplayMove(currentMove-1); /* before DisplayMoveError */
8933         SwitchClocks(forwardMostMove-1); // [HGM] race
8934         DisplayBothClocks();
8935         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8936                 parseList[currentMove], _(cps->which));
8937         DisplayMoveError(buf1);
8938         DrawPosition(FALSE, boards[currentMove]);
8939
8940         SetUserThinkingEnables();
8941         return;
8942     }
8943     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8944         /* Program has a broken "time" command that
8945            outputs a string not ending in newline.
8946            Don't use it. */
8947         cps->sendTime = 0;
8948     }
8949
8950     /*
8951      * If chess program startup fails, exit with an error message.
8952      * Attempts to recover here are futile. [HGM] Well, we try anyway
8953      */
8954     if ((StrStr(message, "unknown host") != NULL)
8955         || (StrStr(message, "No remote directory") != NULL)
8956         || (StrStr(message, "not found") != NULL)
8957         || (StrStr(message, "No such file") != NULL)
8958         || (StrStr(message, "can't alloc") != NULL)
8959         || (StrStr(message, "Permission denied") != NULL)) {
8960
8961         cps->maybeThinking = FALSE;
8962         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8963                 _(cps->which), cps->program, cps->host, message);
8964         RemoveInputSource(cps->isr);
8965         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8966             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8967             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8968         }
8969         return;
8970     }
8971
8972     /*
8973      * Look for hint output
8974      */
8975     if (sscanf(message, "Hint: %s", buf1) == 1) {
8976         if (cps == &first && hintRequested) {
8977             hintRequested = FALSE;
8978             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8979                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8980                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8981                                     PosFlags(forwardMostMove),
8982                                     fromY, fromX, toY, toX, promoChar, buf1);
8983                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8984                 DisplayInformation(buf2);
8985             } else {
8986                 /* Hint move could not be parsed!? */
8987               snprintf(buf2, sizeof(buf2),
8988                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8989                         buf1, _(cps->which));
8990                 DisplayError(buf2, 0);
8991             }
8992         } else {
8993           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8994         }
8995         return;
8996     }
8997
8998     /*
8999      * Ignore other messages if game is not in progress
9000      */
9001     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9002         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9003
9004     /*
9005      * look for win, lose, draw, or draw offer
9006      */
9007     if (strncmp(message, "1-0", 3) == 0) {
9008         char *p, *q, *r = "";
9009         p = strchr(message, '{');
9010         if (p) {
9011             q = strchr(p, '}');
9012             if (q) {
9013                 *q = NULLCHAR;
9014                 r = p + 1;
9015             }
9016         }
9017         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9018         return;
9019     } else if (strncmp(message, "0-1", 3) == 0) {
9020         char *p, *q, *r = "";
9021         p = strchr(message, '{');
9022         if (p) {
9023             q = strchr(p, '}');
9024             if (q) {
9025                 *q = NULLCHAR;
9026                 r = p + 1;
9027             }
9028         }
9029         /* Kludge for Arasan 4.1 bug */
9030         if (strcmp(r, "Black resigns") == 0) {
9031             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9032             return;
9033         }
9034         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9035         return;
9036     } else if (strncmp(message, "1/2", 3) == 0) {
9037         char *p, *q, *r = "";
9038         p = strchr(message, '{');
9039         if (p) {
9040             q = strchr(p, '}');
9041             if (q) {
9042                 *q = NULLCHAR;
9043                 r = p + 1;
9044             }
9045         }
9046
9047         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9048         return;
9049
9050     } else if (strncmp(message, "White resign", 12) == 0) {
9051         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9052         return;
9053     } else if (strncmp(message, "Black resign", 12) == 0) {
9054         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9055         return;
9056     } else if (strncmp(message, "White matches", 13) == 0 ||
9057                strncmp(message, "Black matches", 13) == 0   ) {
9058         /* [HGM] ignore GNUShogi noises */
9059         return;
9060     } else if (strncmp(message, "White", 5) == 0 &&
9061                message[5] != '(' &&
9062                StrStr(message, "Black") == NULL) {
9063         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9064         return;
9065     } else if (strncmp(message, "Black", 5) == 0 &&
9066                message[5] != '(') {
9067         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9068         return;
9069     } else if (strcmp(message, "resign") == 0 ||
9070                strcmp(message, "computer resigns") == 0) {
9071         switch (gameMode) {
9072           case MachinePlaysBlack:
9073           case IcsPlayingBlack:
9074             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9075             break;
9076           case MachinePlaysWhite:
9077           case IcsPlayingWhite:
9078             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9079             break;
9080           case TwoMachinesPlay:
9081             if (cps->twoMachinesColor[0] == 'w')
9082               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9083             else
9084               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9085             break;
9086           default:
9087             /* can't happen */
9088             break;
9089         }
9090         return;
9091     } else if (strncmp(message, "opponent mates", 14) == 0) {
9092         switch (gameMode) {
9093           case MachinePlaysBlack:
9094           case IcsPlayingBlack:
9095             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9096             break;
9097           case MachinePlaysWhite:
9098           case IcsPlayingWhite:
9099             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9100             break;
9101           case TwoMachinesPlay:
9102             if (cps->twoMachinesColor[0] == 'w')
9103               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9104             else
9105               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9106             break;
9107           default:
9108             /* can't happen */
9109             break;
9110         }
9111         return;
9112     } else if (strncmp(message, "computer mates", 14) == 0) {
9113         switch (gameMode) {
9114           case MachinePlaysBlack:
9115           case IcsPlayingBlack:
9116             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9117             break;
9118           case MachinePlaysWhite:
9119           case IcsPlayingWhite:
9120             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9121             break;
9122           case TwoMachinesPlay:
9123             if (cps->twoMachinesColor[0] == 'w')
9124               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9125             else
9126               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9127             break;
9128           default:
9129             /* can't happen */
9130             break;
9131         }
9132         return;
9133     } else if (strncmp(message, "checkmate", 9) == 0) {
9134         if (WhiteOnMove(forwardMostMove)) {
9135             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9136         } else {
9137             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9138         }
9139         return;
9140     } else if (strstr(message, "Draw") != NULL ||
9141                strstr(message, "game is a draw") != NULL) {
9142         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9143         return;
9144     } else if (strstr(message, "offer") != NULL &&
9145                strstr(message, "draw") != NULL) {
9146 #if ZIPPY
9147         if (appData.zippyPlay && first.initDone) {
9148             /* Relay offer to ICS */
9149             SendToICS(ics_prefix);
9150             SendToICS("draw\n");
9151         }
9152 #endif
9153         cps->offeredDraw = 2; /* valid until this engine moves twice */
9154         if (gameMode == TwoMachinesPlay) {
9155             if (cps->other->offeredDraw) {
9156                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9157             /* [HGM] in two-machine mode we delay relaying draw offer      */
9158             /* until after we also have move, to see if it is really claim */
9159             }
9160         } else if (gameMode == MachinePlaysWhite ||
9161                    gameMode == MachinePlaysBlack) {
9162           if (userOfferedDraw) {
9163             DisplayInformation(_("Machine accepts your draw offer"));
9164             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9165           } else {
9166             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9167           }
9168         }
9169     }
9170
9171
9172     /*
9173      * Look for thinking output
9174      */
9175     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9176           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9177                                 ) {
9178         int plylev, mvleft, mvtot, curscore, time;
9179         char mvname[MOVE_LEN];
9180         u64 nodes; // [DM]
9181         char plyext;
9182         int ignore = FALSE;
9183         int prefixHint = FALSE;
9184         mvname[0] = NULLCHAR;
9185
9186         switch (gameMode) {
9187           case MachinePlaysBlack:
9188           case IcsPlayingBlack:
9189             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9190             break;
9191           case MachinePlaysWhite:
9192           case IcsPlayingWhite:
9193             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9194             break;
9195           case AnalyzeMode:
9196           case AnalyzeFile:
9197             break;
9198           case IcsObserving: /* [DM] icsEngineAnalyze */
9199             if (!appData.icsEngineAnalyze) ignore = TRUE;
9200             break;
9201           case TwoMachinesPlay:
9202             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9203                 ignore = TRUE;
9204             }
9205             break;
9206           default:
9207             ignore = TRUE;
9208             break;
9209         }
9210
9211         if (!ignore) {
9212             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9213             buf1[0] = NULLCHAR;
9214             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9215                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9216
9217                 if (plyext != ' ' && plyext != '\t') {
9218                     time *= 100;
9219                 }
9220
9221                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9222                 if( cps->scoreIsAbsolute &&
9223                     ( gameMode == MachinePlaysBlack ||
9224                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9225                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9226                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9227                      !WhiteOnMove(currentMove)
9228                     ) )
9229                 {
9230                     curscore = -curscore;
9231                 }
9232
9233                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9234
9235                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9236                         char buf[MSG_SIZ];
9237                         FILE *f;
9238                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9239                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9240                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9241                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9242                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9243                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9244                                 fclose(f);
9245                         }
9246                         else
9247                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9248                           DisplayError(_("failed writing PV"), 0);
9249                 }
9250
9251                 tempStats.depth = plylev;
9252                 tempStats.nodes = nodes;
9253                 tempStats.time = time;
9254                 tempStats.score = curscore;
9255                 tempStats.got_only_move = 0;
9256
9257                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9258                         int ticklen;
9259
9260                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9261                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9262                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9263                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9264                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9265                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9266                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9267                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9268                 }
9269
9270                 /* Buffer overflow protection */
9271                 if (pv[0] != NULLCHAR) {
9272                     if (strlen(pv) >= sizeof(tempStats.movelist)
9273                         && appData.debugMode) {
9274                         fprintf(debugFP,
9275                                 "PV is too long; using the first %u bytes.\n",
9276                                 (unsigned) sizeof(tempStats.movelist) - 1);
9277                     }
9278
9279                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9280                 } else {
9281                     sprintf(tempStats.movelist, " no PV\n");
9282                 }
9283
9284                 if (tempStats.seen_stat) {
9285                     tempStats.ok_to_send = 1;
9286                 }
9287
9288                 if (strchr(tempStats.movelist, '(') != NULL) {
9289                     tempStats.line_is_book = 1;
9290                     tempStats.nr_moves = 0;
9291                     tempStats.moves_left = 0;
9292                 } else {
9293                     tempStats.line_is_book = 0;
9294                 }
9295
9296                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9297                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9298
9299                 SendProgramStatsToFrontend( cps, &tempStats );
9300
9301                 /*
9302                     [AS] Protect the thinkOutput buffer from overflow... this
9303                     is only useful if buf1 hasn't overflowed first!
9304                 */
9305                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9306                          plylev,
9307                          (gameMode == TwoMachinesPlay ?
9308                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9309                          ((double) curscore) / 100.0,
9310                          prefixHint ? lastHint : "",
9311                          prefixHint ? " " : "" );
9312
9313                 if( buf1[0] != NULLCHAR ) {
9314                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9315
9316                     if( strlen(pv) > max_len ) {
9317                         if( appData.debugMode) {
9318                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9319                         }
9320                         pv[max_len+1] = '\0';
9321                     }
9322
9323                     strcat( thinkOutput, pv);
9324                 }
9325
9326                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9327                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9328                     DisplayMove(currentMove - 1);
9329                 }
9330                 return;
9331
9332             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9333                 /* crafty (9.25+) says "(only move) <move>"
9334                  * if there is only 1 legal move
9335                  */
9336                 sscanf(p, "(only move) %s", buf1);
9337                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9338                 sprintf(programStats.movelist, "%s (only move)", buf1);
9339                 programStats.depth = 1;
9340                 programStats.nr_moves = 1;
9341                 programStats.moves_left = 1;
9342                 programStats.nodes = 1;
9343                 programStats.time = 1;
9344                 programStats.got_only_move = 1;
9345
9346                 /* Not really, but we also use this member to
9347                    mean "line isn't going to change" (Crafty
9348                    isn't searching, so stats won't change) */
9349                 programStats.line_is_book = 1;
9350
9351                 SendProgramStatsToFrontend( cps, &programStats );
9352
9353                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9354                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9355                     DisplayMove(currentMove - 1);
9356                 }
9357                 return;
9358             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9359                               &time, &nodes, &plylev, &mvleft,
9360                               &mvtot, mvname) >= 5) {
9361                 /* The stat01: line is from Crafty (9.29+) in response
9362                    to the "." command */
9363                 programStats.seen_stat = 1;
9364                 cps->maybeThinking = TRUE;
9365
9366                 if (programStats.got_only_move || !appData.periodicUpdates)
9367                   return;
9368
9369                 programStats.depth = plylev;
9370                 programStats.time = time;
9371                 programStats.nodes = nodes;
9372                 programStats.moves_left = mvleft;
9373                 programStats.nr_moves = mvtot;
9374                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9375                 programStats.ok_to_send = 1;
9376                 programStats.movelist[0] = '\0';
9377
9378                 SendProgramStatsToFrontend( cps, &programStats );
9379
9380                 return;
9381
9382             } else if (strncmp(message,"++",2) == 0) {
9383                 /* Crafty 9.29+ outputs this */
9384                 programStats.got_fail = 2;
9385                 return;
9386
9387             } else if (strncmp(message,"--",2) == 0) {
9388                 /* Crafty 9.29+ outputs this */
9389                 programStats.got_fail = 1;
9390                 return;
9391
9392             } else if (thinkOutput[0] != NULLCHAR &&
9393                        strncmp(message, "    ", 4) == 0) {
9394                 unsigned message_len;
9395
9396                 p = message;
9397                 while (*p && *p == ' ') p++;
9398
9399                 message_len = strlen( p );
9400
9401                 /* [AS] Avoid buffer overflow */
9402                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9403                     strcat(thinkOutput, " ");
9404                     strcat(thinkOutput, p);
9405                 }
9406
9407                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9408                     strcat(programStats.movelist, " ");
9409                     strcat(programStats.movelist, p);
9410                 }
9411
9412                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9413                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9414                     DisplayMove(currentMove - 1);
9415                 }
9416                 return;
9417             }
9418         }
9419         else {
9420             buf1[0] = NULLCHAR;
9421
9422             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9423                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9424             {
9425                 ChessProgramStats cpstats;
9426
9427                 if (plyext != ' ' && plyext != '\t') {
9428                     time *= 100;
9429                 }
9430
9431                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9432                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9433                     curscore = -curscore;
9434                 }
9435
9436                 cpstats.depth = plylev;
9437                 cpstats.nodes = nodes;
9438                 cpstats.time = time;
9439                 cpstats.score = curscore;
9440                 cpstats.got_only_move = 0;
9441                 cpstats.movelist[0] = '\0';
9442
9443                 if (buf1[0] != NULLCHAR) {
9444                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9445                 }
9446
9447                 cpstats.ok_to_send = 0;
9448                 cpstats.line_is_book = 0;
9449                 cpstats.nr_moves = 0;
9450                 cpstats.moves_left = 0;
9451
9452                 SendProgramStatsToFrontend( cps, &cpstats );
9453             }
9454         }
9455     }
9456 }
9457
9458
9459 /* Parse a game score from the character string "game", and
9460    record it as the history of the current game.  The game
9461    score is NOT assumed to start from the standard position.
9462    The display is not updated in any way.
9463    */
9464 void
9465 ParseGameHistory (char *game)
9466 {
9467     ChessMove moveType;
9468     int fromX, fromY, toX, toY, boardIndex;
9469     char promoChar;
9470     char *p, *q;
9471     char buf[MSG_SIZ];
9472
9473     if (appData.debugMode)
9474       fprintf(debugFP, "Parsing game history: %s\n", game);
9475
9476     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9477     gameInfo.site = StrSave(appData.icsHost);
9478     gameInfo.date = PGNDate();
9479     gameInfo.round = StrSave("-");
9480
9481     /* Parse out names of players */
9482     while (*game == ' ') game++;
9483     p = buf;
9484     while (*game != ' ') *p++ = *game++;
9485     *p = NULLCHAR;
9486     gameInfo.white = StrSave(buf);
9487     while (*game == ' ') game++;
9488     p = buf;
9489     while (*game != ' ' && *game != '\n') *p++ = *game++;
9490     *p = NULLCHAR;
9491     gameInfo.black = StrSave(buf);
9492
9493     /* Parse moves */
9494     boardIndex = blackPlaysFirst ? 1 : 0;
9495     yynewstr(game);
9496     for (;;) {
9497         yyboardindex = boardIndex;
9498         moveType = (ChessMove) Myylex();
9499         switch (moveType) {
9500           case IllegalMove:             /* maybe suicide chess, etc. */
9501   if (appData.debugMode) {
9502     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9503     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9504     setbuf(debugFP, NULL);
9505   }
9506           case WhitePromotion:
9507           case BlackPromotion:
9508           case WhiteNonPromotion:
9509           case BlackNonPromotion:
9510           case NormalMove:
9511           case FirstLeg:
9512           case WhiteCapturesEnPassant:
9513           case BlackCapturesEnPassant:
9514           case WhiteKingSideCastle:
9515           case WhiteQueenSideCastle:
9516           case BlackKingSideCastle:
9517           case BlackQueenSideCastle:
9518           case WhiteKingSideCastleWild:
9519           case WhiteQueenSideCastleWild:
9520           case BlackKingSideCastleWild:
9521           case BlackQueenSideCastleWild:
9522           /* PUSH Fabien */
9523           case WhiteHSideCastleFR:
9524           case WhiteASideCastleFR:
9525           case BlackHSideCastleFR:
9526           case BlackASideCastleFR:
9527           /* POP Fabien */
9528             fromX = currentMoveString[0] - AAA;
9529             fromY = currentMoveString[1] - ONE;
9530             toX = currentMoveString[2] - AAA;
9531             toY = currentMoveString[3] - ONE;
9532             promoChar = currentMoveString[4];
9533             break;
9534           case WhiteDrop:
9535           case BlackDrop:
9536             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9537             fromX = moveType == WhiteDrop ?
9538               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9539             (int) CharToPiece(ToLower(currentMoveString[0]));
9540             fromY = DROP_RANK;
9541             toX = currentMoveString[2] - AAA;
9542             toY = currentMoveString[3] - ONE;
9543             promoChar = NULLCHAR;
9544             break;
9545           case AmbiguousMove:
9546             /* bug? */
9547             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9548   if (appData.debugMode) {
9549     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9550     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9551     setbuf(debugFP, NULL);
9552   }
9553             DisplayError(buf, 0);
9554             return;
9555           case ImpossibleMove:
9556             /* bug? */
9557             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9558   if (appData.debugMode) {
9559     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9560     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9561     setbuf(debugFP, NULL);
9562   }
9563             DisplayError(buf, 0);
9564             return;
9565           case EndOfFile:
9566             if (boardIndex < backwardMostMove) {
9567                 /* Oops, gap.  How did that happen? */
9568                 DisplayError(_("Gap in move list"), 0);
9569                 return;
9570             }
9571             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9572             if (boardIndex > forwardMostMove) {
9573                 forwardMostMove = boardIndex;
9574             }
9575             return;
9576           case ElapsedTime:
9577             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9578                 strcat(parseList[boardIndex-1], " ");
9579                 strcat(parseList[boardIndex-1], yy_text);
9580             }
9581             continue;
9582           case Comment:
9583           case PGNTag:
9584           case NAG:
9585           default:
9586             /* ignore */
9587             continue;
9588           case WhiteWins:
9589           case BlackWins:
9590           case GameIsDrawn:
9591           case GameUnfinished:
9592             if (gameMode == IcsExamining) {
9593                 if (boardIndex < backwardMostMove) {
9594                     /* Oops, gap.  How did that happen? */
9595                     return;
9596                 }
9597                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9598                 return;
9599             }
9600             gameInfo.result = moveType;
9601             p = strchr(yy_text, '{');
9602             if (p == NULL) p = strchr(yy_text, '(');
9603             if (p == NULL) {
9604                 p = yy_text;
9605                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9606             } else {
9607                 q = strchr(p, *p == '{' ? '}' : ')');
9608                 if (q != NULL) *q = NULLCHAR;
9609                 p++;
9610             }
9611             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9612             gameInfo.resultDetails = StrSave(p);
9613             continue;
9614         }
9615         if (boardIndex >= forwardMostMove &&
9616             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9617             backwardMostMove = blackPlaysFirst ? 1 : 0;
9618             return;
9619         }
9620         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9621                                  fromY, fromX, toY, toX, promoChar,
9622                                  parseList[boardIndex]);
9623         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9624         /* currentMoveString is set as a side-effect of yylex */
9625         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9626         strcat(moveList[boardIndex], "\n");
9627         boardIndex++;
9628         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9629         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9630           case MT_NONE:
9631           case MT_STALEMATE:
9632           default:
9633             break;
9634           case MT_CHECK:
9635             if(gameInfo.variant != VariantShogi)
9636                 strcat(parseList[boardIndex - 1], "+");
9637             break;
9638           case MT_CHECKMATE:
9639           case MT_STAINMATE:
9640             strcat(parseList[boardIndex - 1], "#");
9641             break;
9642         }
9643     }
9644 }
9645
9646
9647 /* Apply a move to the given board  */
9648 void
9649 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9650 {
9651   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9652   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9653
9654     /* [HGM] compute & store e.p. status and castling rights for new position */
9655     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9656
9657       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9658       oldEP = (signed char)board[EP_STATUS];
9659       board[EP_STATUS] = EP_NONE;
9660
9661   if (fromY == DROP_RANK) {
9662         /* must be first */
9663         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9664             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9665             return;
9666         }
9667         piece = board[toY][toX] = (ChessSquare) fromX;
9668   } else {
9669       ChessSquare victim;
9670       int i;
9671
9672       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9673            victim = board[killY][killX],
9674            board[killY][killX] = EmptySquare,
9675            board[EP_STATUS] = EP_CAPTURE;
9676
9677       if( board[toY][toX] != EmptySquare ) {
9678            board[EP_STATUS] = EP_CAPTURE;
9679            if( (fromX != toX || fromY != toY) && // not igui!
9680                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9681                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9682                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9683            }
9684       }
9685
9686       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9687            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9688                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9689       } else
9690       if( board[fromY][fromX] == WhitePawn ) {
9691            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9692                board[EP_STATUS] = EP_PAWN_MOVE;
9693            if( toY-fromY==2) {
9694                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9695                         gameInfo.variant != VariantBerolina || toX < fromX)
9696                       board[EP_STATUS] = toX | berolina;
9697                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9698                         gameInfo.variant != VariantBerolina || toX > fromX)
9699                       board[EP_STATUS] = toX;
9700            }
9701       } else
9702       if( board[fromY][fromX] == BlackPawn ) {
9703            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9704                board[EP_STATUS] = EP_PAWN_MOVE;
9705            if( toY-fromY== -2) {
9706                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9707                         gameInfo.variant != VariantBerolina || toX < fromX)
9708                       board[EP_STATUS] = toX | berolina;
9709                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9710                         gameInfo.variant != VariantBerolina || toX > fromX)
9711                       board[EP_STATUS] = toX;
9712            }
9713        }
9714
9715        for(i=0; i<nrCastlingRights; i++) {
9716            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9717               board[CASTLING][i] == toX   && castlingRank[i] == toY
9718              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9719        }
9720
9721        if(gameInfo.variant == VariantSChess) { // update virginity
9722            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9723            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9724            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9725            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9726        }
9727
9728      if (fromX == toX && fromY == toY) return;
9729
9730      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9731      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9732      if(gameInfo.variant == VariantKnightmate)
9733          king += (int) WhiteUnicorn - (int) WhiteKing;
9734
9735     /* Code added by Tord: */
9736     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9737     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9738         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9739       board[fromY][fromX] = EmptySquare;
9740       board[toY][toX] = EmptySquare;
9741       if((toX > fromX) != (piece == WhiteRook)) {
9742         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9743       } else {
9744         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9745       }
9746     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9747                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9748       board[fromY][fromX] = EmptySquare;
9749       board[toY][toX] = EmptySquare;
9750       if((toX > fromX) != (piece == BlackRook)) {
9751         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9752       } else {
9753         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9754       }
9755     /* End of code added by Tord */
9756
9757     } else if (board[fromY][fromX] == king
9758         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9759         && toY == fromY && toX > fromX+1) {
9760         board[fromY][fromX] = EmptySquare;
9761         board[toY][toX] = king;
9762         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9763         board[fromY][BOARD_RGHT-1] = EmptySquare;
9764     } else if (board[fromY][fromX] == king
9765         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9766                && toY == fromY && toX < fromX-1) {
9767         board[fromY][fromX] = EmptySquare;
9768         board[toY][toX] = king;
9769         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9770         board[fromY][BOARD_LEFT] = EmptySquare;
9771     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9772                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9773                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9774                ) {
9775         /* white pawn promotion */
9776         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9777         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9778             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9779         board[fromY][fromX] = EmptySquare;
9780     } else if ((fromY >= BOARD_HEIGHT>>1)
9781                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9782                && (toX != fromX)
9783                && gameInfo.variant != VariantXiangqi
9784                && gameInfo.variant != VariantBerolina
9785                && (board[fromY][fromX] == WhitePawn)
9786                && (board[toY][toX] == EmptySquare)) {
9787         board[fromY][fromX] = EmptySquare;
9788         board[toY][toX] = WhitePawn;
9789         captured = board[toY - 1][toX];
9790         board[toY - 1][toX] = EmptySquare;
9791     } else if ((fromY == BOARD_HEIGHT-4)
9792                && (toX == fromX)
9793                && gameInfo.variant == VariantBerolina
9794                && (board[fromY][fromX] == WhitePawn)
9795                && (board[toY][toX] == EmptySquare)) {
9796         board[fromY][fromX] = EmptySquare;
9797         board[toY][toX] = WhitePawn;
9798         if(oldEP & EP_BEROLIN_A) {
9799                 captured = board[fromY][fromX-1];
9800                 board[fromY][fromX-1] = EmptySquare;
9801         }else{  captured = board[fromY][fromX+1];
9802                 board[fromY][fromX+1] = EmptySquare;
9803         }
9804     } else if (board[fromY][fromX] == king
9805         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9806                && toY == fromY && toX > fromX+1) {
9807         board[fromY][fromX] = EmptySquare;
9808         board[toY][toX] = king;
9809         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9810         board[fromY][BOARD_RGHT-1] = EmptySquare;
9811     } else if (board[fromY][fromX] == king
9812         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9813                && toY == fromY && toX < fromX-1) {
9814         board[fromY][fromX] = EmptySquare;
9815         board[toY][toX] = king;
9816         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9817         board[fromY][BOARD_LEFT] = EmptySquare;
9818     } else if (fromY == 7 && fromX == 3
9819                && board[fromY][fromX] == BlackKing
9820                && toY == 7 && toX == 5) {
9821         board[fromY][fromX] = EmptySquare;
9822         board[toY][toX] = BlackKing;
9823         board[fromY][7] = EmptySquare;
9824         board[toY][4] = BlackRook;
9825     } else if (fromY == 7 && fromX == 3
9826                && board[fromY][fromX] == BlackKing
9827                && toY == 7 && toX == 1) {
9828         board[fromY][fromX] = EmptySquare;
9829         board[toY][toX] = BlackKing;
9830         board[fromY][0] = EmptySquare;
9831         board[toY][2] = BlackRook;
9832     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9833                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9834                && toY < promoRank && promoChar
9835                ) {
9836         /* black pawn promotion */
9837         board[toY][toX] = CharToPiece(ToLower(promoChar));
9838         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9839             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9840         board[fromY][fromX] = EmptySquare;
9841     } else if ((fromY < BOARD_HEIGHT>>1)
9842                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9843                && (toX != fromX)
9844                && gameInfo.variant != VariantXiangqi
9845                && gameInfo.variant != VariantBerolina
9846                && (board[fromY][fromX] == BlackPawn)
9847                && (board[toY][toX] == EmptySquare)) {
9848         board[fromY][fromX] = EmptySquare;
9849         board[toY][toX] = BlackPawn;
9850         captured = board[toY + 1][toX];
9851         board[toY + 1][toX] = EmptySquare;
9852     } else if ((fromY == 3)
9853                && (toX == fromX)
9854                && gameInfo.variant == VariantBerolina
9855                && (board[fromY][fromX] == BlackPawn)
9856                && (board[toY][toX] == EmptySquare)) {
9857         board[fromY][fromX] = EmptySquare;
9858         board[toY][toX] = BlackPawn;
9859         if(oldEP & EP_BEROLIN_A) {
9860                 captured = board[fromY][fromX-1];
9861                 board[fromY][fromX-1] = EmptySquare;
9862         }else{  captured = board[fromY][fromX+1];
9863                 board[fromY][fromX+1] = EmptySquare;
9864         }
9865     } else {
9866         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9867         board[fromY][fromX] = EmptySquare;
9868         board[toY][toX] = piece;
9869     }
9870   }
9871
9872     if (gameInfo.holdingsWidth != 0) {
9873
9874       /* !!A lot more code needs to be written to support holdings  */
9875       /* [HGM] OK, so I have written it. Holdings are stored in the */
9876       /* penultimate board files, so they are automaticlly stored   */
9877       /* in the game history.                                       */
9878       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9879                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9880         /* Delete from holdings, by decreasing count */
9881         /* and erasing image if necessary            */
9882         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9883         if(p < (int) BlackPawn) { /* white drop */
9884              p -= (int)WhitePawn;
9885                  p = PieceToNumber((ChessSquare)p);
9886              if(p >= gameInfo.holdingsSize) p = 0;
9887              if(--board[p][BOARD_WIDTH-2] <= 0)
9888                   board[p][BOARD_WIDTH-1] = EmptySquare;
9889              if((int)board[p][BOARD_WIDTH-2] < 0)
9890                         board[p][BOARD_WIDTH-2] = 0;
9891         } else {                  /* black drop */
9892              p -= (int)BlackPawn;
9893                  p = PieceToNumber((ChessSquare)p);
9894              if(p >= gameInfo.holdingsSize) p = 0;
9895              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9896                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9897              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9898                         board[BOARD_HEIGHT-1-p][1] = 0;
9899         }
9900       }
9901       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9902           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9903         /* [HGM] holdings: Add to holdings, if holdings exist */
9904         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9905                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9906                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9907         }
9908         p = (int) captured;
9909         if (p >= (int) BlackPawn) {
9910           p -= (int)BlackPawn;
9911           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9912                   /* in Shogi restore piece to its original  first */
9913                   captured = (ChessSquare) (DEMOTED captured);
9914                   p = DEMOTED p;
9915           }
9916           p = PieceToNumber((ChessSquare)p);
9917           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9918           board[p][BOARD_WIDTH-2]++;
9919           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9920         } else {
9921           p -= (int)WhitePawn;
9922           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9923                   captured = (ChessSquare) (DEMOTED captured);
9924                   p = DEMOTED p;
9925           }
9926           p = PieceToNumber((ChessSquare)p);
9927           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9928           board[BOARD_HEIGHT-1-p][1]++;
9929           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9930         }
9931       }
9932     } else if (gameInfo.variant == VariantAtomic) {
9933       if (captured != EmptySquare) {
9934         int y, x;
9935         for (y = toY-1; y <= toY+1; y++) {
9936           for (x = toX-1; x <= toX+1; x++) {
9937             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9938                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9939               board[y][x] = EmptySquare;
9940             }
9941           }
9942         }
9943         board[toY][toX] = EmptySquare;
9944       }
9945     }
9946     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9947         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9948     } else
9949     if(promoChar == '+') {
9950         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9951         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9952     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9953         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9954         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9955            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9956         board[toY][toX] = newPiece;
9957     }
9958     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9959                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9960         // [HGM] superchess: take promotion piece out of holdings
9961         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9962         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9963             if(!--board[k][BOARD_WIDTH-2])
9964                 board[k][BOARD_WIDTH-1] = EmptySquare;
9965         } else {
9966             if(!--board[BOARD_HEIGHT-1-k][1])
9967                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9968         }
9969     }
9970 }
9971
9972 /* Updates forwardMostMove */
9973 void
9974 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9975 {
9976     int x = toX, y = toY;
9977     char *s = parseList[forwardMostMove];
9978     ChessSquare p = boards[forwardMostMove][toY][toX];
9979 //    forwardMostMove++; // [HGM] bare: moved downstream
9980
9981     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9982     (void) CoordsToAlgebraic(boards[forwardMostMove],
9983                              PosFlags(forwardMostMove),
9984                              fromY, fromX, y, x, promoChar,
9985                              s);
9986     if(killX >= 0 && killY >= 0)
9987         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9988
9989     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9990         int timeLeft; static int lastLoadFlag=0; int king, piece;
9991         piece = boards[forwardMostMove][fromY][fromX];
9992         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9993         if(gameInfo.variant == VariantKnightmate)
9994             king += (int) WhiteUnicorn - (int) WhiteKing;
9995         if(forwardMostMove == 0) {
9996             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9997                 fprintf(serverMoves, "%s;", UserName());
9998             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9999                 fprintf(serverMoves, "%s;", second.tidy);
10000             fprintf(serverMoves, "%s;", first.tidy);
10001             if(gameMode == MachinePlaysWhite)
10002                 fprintf(serverMoves, "%s;", UserName());
10003             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10004                 fprintf(serverMoves, "%s;", second.tidy);
10005         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10006         lastLoadFlag = loadFlag;
10007         // print base move
10008         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10009         // print castling suffix
10010         if( toY == fromY && piece == king ) {
10011             if(toX-fromX > 1)
10012                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10013             if(fromX-toX >1)
10014                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10015         }
10016         // e.p. suffix
10017         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10018              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10019              boards[forwardMostMove][toY][toX] == EmptySquare
10020              && fromX != toX && fromY != toY)
10021                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10022         // promotion suffix
10023         if(promoChar != NULLCHAR) {
10024             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10025                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10026                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10027             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10028         }
10029         if(!loadFlag) {
10030                 char buf[MOVE_LEN*2], *p; int len;
10031             fprintf(serverMoves, "/%d/%d",
10032                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10033             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10034             else                      timeLeft = blackTimeRemaining/1000;
10035             fprintf(serverMoves, "/%d", timeLeft);
10036                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10037                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10038                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10039                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10040             fprintf(serverMoves, "/%s", buf);
10041         }
10042         fflush(serverMoves);
10043     }
10044
10045     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10046         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10047       return;
10048     }
10049     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10050     if (commentList[forwardMostMove+1] != NULL) {
10051         free(commentList[forwardMostMove+1]);
10052         commentList[forwardMostMove+1] = NULL;
10053     }
10054     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10055     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10056     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10057     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10058     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10059     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10060     adjustedClock = FALSE;
10061     gameInfo.result = GameUnfinished;
10062     if (gameInfo.resultDetails != NULL) {
10063         free(gameInfo.resultDetails);
10064         gameInfo.resultDetails = NULL;
10065     }
10066     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10067                               moveList[forwardMostMove - 1]);
10068     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10069       case MT_NONE:
10070       case MT_STALEMATE:
10071       default:
10072         break;
10073       case MT_CHECK:
10074         if(gameInfo.variant != VariantShogi)
10075             strcat(parseList[forwardMostMove - 1], "+");
10076         break;
10077       case MT_CHECKMATE:
10078       case MT_STAINMATE:
10079         strcat(parseList[forwardMostMove - 1], "#");
10080         break;
10081     }
10082 }
10083
10084 /* Updates currentMove if not pausing */
10085 void
10086 ShowMove (int fromX, int fromY, int toX, int toY)
10087 {
10088     int instant = (gameMode == PlayFromGameFile) ?
10089         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10090     if(appData.noGUI) return;
10091     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10092         if (!instant) {
10093             if (forwardMostMove == currentMove + 1) {
10094                 AnimateMove(boards[forwardMostMove - 1],
10095                             fromX, fromY, toX, toY);
10096             }
10097         }
10098         currentMove = forwardMostMove;
10099     }
10100
10101     killX = killY = -1; // [HGM] lion: used up
10102
10103     if (instant) return;
10104
10105     DisplayMove(currentMove - 1);
10106     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10107             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10108                 SetHighlights(fromX, fromY, toX, toY);
10109             }
10110     }
10111     DrawPosition(FALSE, boards[currentMove]);
10112     DisplayBothClocks();
10113     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10114 }
10115
10116 void
10117 SendEgtPath (ChessProgramState *cps)
10118 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10119         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10120
10121         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10122
10123         while(*p) {
10124             char c, *q = name+1, *r, *s;
10125
10126             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10127             while(*p && *p != ',') *q++ = *p++;
10128             *q++ = ':'; *q = 0;
10129             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10130                 strcmp(name, ",nalimov:") == 0 ) {
10131                 // take nalimov path from the menu-changeable option first, if it is defined
10132               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10133                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10134             } else
10135             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10136                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10137                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10138                 s = r = StrStr(s, ":") + 1; // beginning of path info
10139                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10140                 c = *r; *r = 0;             // temporarily null-terminate path info
10141                     *--q = 0;               // strip of trailig ':' from name
10142                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10143                 *r = c;
10144                 SendToProgram(buf,cps);     // send egtbpath command for this format
10145             }
10146             if(*p == ',') p++; // read away comma to position for next format name
10147         }
10148 }
10149
10150 static int
10151 NonStandardBoardSize ()
10152 {
10153       /* [HGM] Awkward testing. Should really be a table */
10154       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10155       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10156       if( gameInfo.variant == VariantXiangqi )
10157            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10158       if( gameInfo.variant == VariantShogi )
10159            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10160       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10161            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10162       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10163           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10164            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10165       if( gameInfo.variant == VariantCourier )
10166            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10167       if( gameInfo.variant == VariantSuper )
10168            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10169       if( gameInfo.variant == VariantGreat )
10170            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10171       if( gameInfo.variant == VariantSChess )
10172            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10173       if( gameInfo.variant == VariantGrand )
10174            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10175       if( gameInfo.variant == VariantChu )
10176            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10177       return overruled;
10178 }
10179
10180 void
10181 InitChessProgram (ChessProgramState *cps, int setup)
10182 /* setup needed to setup FRC opening position */
10183 {
10184     char buf[MSG_SIZ], b[MSG_SIZ];
10185     if (appData.noChessProgram) return;
10186     hintRequested = FALSE;
10187     bookRequested = FALSE;
10188
10189     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10190     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10191     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10192     if(cps->memSize) { /* [HGM] memory */
10193       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10194         SendToProgram(buf, cps);
10195     }
10196     SendEgtPath(cps); /* [HGM] EGT */
10197     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10198       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10199         SendToProgram(buf, cps);
10200     }
10201
10202     SendToProgram(cps->initString, cps);
10203     if (gameInfo.variant != VariantNormal &&
10204         gameInfo.variant != VariantLoadable
10205         /* [HGM] also send variant if board size non-standard */
10206         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10207                                             ) {
10208       char *v = VariantName(gameInfo.variant);
10209       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10210         /* [HGM] in protocol 1 we have to assume all variants valid */
10211         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10212         DisplayFatalError(buf, 0, 1);
10213         return;
10214       }
10215
10216       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10217         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10218                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10219            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10220            if(StrStr(cps->variants, b) == NULL) {
10221                // specific sized variant not known, check if general sizing allowed
10222                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10223                    if(StrStr(cps->variants, "boardsize") == NULL) {
10224                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10225                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10226                        DisplayFatalError(buf, 0, 1);
10227                        return;
10228                    }
10229                    /* [HGM] here we really should compare with the maximum supported board size */
10230                }
10231            }
10232       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10233       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10234       SendToProgram(buf, cps);
10235     }
10236     currentlyInitializedVariant = gameInfo.variant;
10237
10238     /* [HGM] send opening position in FRC to first engine */
10239     if(setup) {
10240           SendToProgram("force\n", cps);
10241           SendBoard(cps, 0);
10242           /* engine is now in force mode! Set flag to wake it up after first move. */
10243           setboardSpoiledMachineBlack = 1;
10244     }
10245
10246     if (cps->sendICS) {
10247       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10248       SendToProgram(buf, cps);
10249     }
10250     cps->maybeThinking = FALSE;
10251     cps->offeredDraw = 0;
10252     if (!appData.icsActive) {
10253         SendTimeControl(cps, movesPerSession, timeControl,
10254                         timeIncrement, appData.searchDepth,
10255                         searchTime);
10256     }
10257     if (appData.showThinking
10258         // [HGM] thinking: four options require thinking output to be sent
10259         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10260                                 ) {
10261         SendToProgram("post\n", cps);
10262     }
10263     SendToProgram("hard\n", cps);
10264     if (!appData.ponderNextMove) {
10265         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10266            it without being sure what state we are in first.  "hard"
10267            is not a toggle, so that one is OK.
10268          */
10269         SendToProgram("easy\n", cps);
10270     }
10271     if (cps->usePing) {
10272       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10273       SendToProgram(buf, cps);
10274     }
10275     cps->initDone = TRUE;
10276     ClearEngineOutputPane(cps == &second);
10277 }
10278
10279
10280 void
10281 ResendOptions (ChessProgramState *cps)
10282 { // send the stored value of the options
10283   int i;
10284   char buf[MSG_SIZ];
10285   Option *opt = cps->option;
10286   for(i=0; i<cps->nrOptions; i++, opt++) {
10287       switch(opt->type) {
10288         case Spin:
10289         case Slider:
10290         case CheckBox:
10291             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10292           break;
10293         case ComboBox:
10294           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10295           break;
10296         default:
10297             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10298           break;
10299         case Button:
10300         case SaveButton:
10301           continue;
10302       }
10303       SendToProgram(buf, cps);
10304   }
10305 }
10306
10307 void
10308 StartChessProgram (ChessProgramState *cps)
10309 {
10310     char buf[MSG_SIZ];
10311     int err;
10312
10313     if (appData.noChessProgram) return;
10314     cps->initDone = FALSE;
10315
10316     if (strcmp(cps->host, "localhost") == 0) {
10317         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10318     } else if (*appData.remoteShell == NULLCHAR) {
10319         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10320     } else {
10321         if (*appData.remoteUser == NULLCHAR) {
10322           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10323                     cps->program);
10324         } else {
10325           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10326                     cps->host, appData.remoteUser, cps->program);
10327         }
10328         err = StartChildProcess(buf, "", &cps->pr);
10329     }
10330
10331     if (err != 0) {
10332       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10333         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10334         if(cps != &first) return;
10335         appData.noChessProgram = TRUE;
10336         ThawUI();
10337         SetNCPMode();
10338 //      DisplayFatalError(buf, err, 1);
10339 //      cps->pr = NoProc;
10340 //      cps->isr = NULL;
10341         return;
10342     }
10343
10344     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10345     if (cps->protocolVersion > 1) {
10346       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10347       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10348         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10349         cps->comboCnt = 0;  //                and values of combo boxes
10350       }
10351       SendToProgram(buf, cps);
10352       if(cps->reload) ResendOptions(cps);
10353     } else {
10354       SendToProgram("xboard\n", cps);
10355     }
10356 }
10357
10358 void
10359 TwoMachinesEventIfReady P((void))
10360 {
10361   static int curMess = 0;
10362   if (first.lastPing != first.lastPong) {
10363     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10364     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10365     return;
10366   }
10367   if (second.lastPing != second.lastPong) {
10368     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10369     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10370     return;
10371   }
10372   DisplayMessage("", ""); curMess = 0;
10373   TwoMachinesEvent();
10374 }
10375
10376 char *
10377 MakeName (char *template)
10378 {
10379     time_t clock;
10380     struct tm *tm;
10381     static char buf[MSG_SIZ];
10382     char *p = buf;
10383     int i;
10384
10385     clock = time((time_t *)NULL);
10386     tm = localtime(&clock);
10387
10388     while(*p++ = *template++) if(p[-1] == '%') {
10389         switch(*template++) {
10390           case 0:   *p = 0; return buf;
10391           case 'Y': i = tm->tm_year+1900; break;
10392           case 'y': i = tm->tm_year-100; break;
10393           case 'M': i = tm->tm_mon+1; break;
10394           case 'd': i = tm->tm_mday; break;
10395           case 'h': i = tm->tm_hour; break;
10396           case 'm': i = tm->tm_min; break;
10397           case 's': i = tm->tm_sec; break;
10398           default:  i = 0;
10399         }
10400         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10401     }
10402     return buf;
10403 }
10404
10405 int
10406 CountPlayers (char *p)
10407 {
10408     int n = 0;
10409     while(p = strchr(p, '\n')) p++, n++; // count participants
10410     return n;
10411 }
10412
10413 FILE *
10414 WriteTourneyFile (char *results, FILE *f)
10415 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10416     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10417     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10418         // create a file with tournament description
10419         fprintf(f, "-participants {%s}\n", appData.participants);
10420         fprintf(f, "-seedBase %d\n", appData.seedBase);
10421         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10422         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10423         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10424         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10425         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10426         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10427         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10428         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10429         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10430         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10431         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10432         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10433         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10434         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10435         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10436         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10437         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10438         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10439         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10440         fprintf(f, "-smpCores %d\n", appData.smpCores);
10441         if(searchTime > 0)
10442                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10443         else {
10444                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10445                 fprintf(f, "-tc %s\n", appData.timeControl);
10446                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10447         }
10448         fprintf(f, "-results \"%s\"\n", results);
10449     }
10450     return f;
10451 }
10452
10453 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10454
10455 void
10456 Substitute (char *participants, int expunge)
10457 {
10458     int i, changed, changes=0, nPlayers=0;
10459     char *p, *q, *r, buf[MSG_SIZ];
10460     if(participants == NULL) return;
10461     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10462     r = p = participants; q = appData.participants;
10463     while(*p && *p == *q) {
10464         if(*p == '\n') r = p+1, nPlayers++;
10465         p++; q++;
10466     }
10467     if(*p) { // difference
10468         while(*p && *p++ != '\n');
10469         while(*q && *q++ != '\n');
10470       changed = nPlayers;
10471         changes = 1 + (strcmp(p, q) != 0);
10472     }
10473     if(changes == 1) { // a single engine mnemonic was changed
10474         q = r; while(*q) nPlayers += (*q++ == '\n');
10475         p = buf; while(*r && (*p = *r++) != '\n') p++;
10476         *p = NULLCHAR;
10477         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10478         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10479         if(mnemonic[i]) { // The substitute is valid
10480             FILE *f;
10481             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10482                 flock(fileno(f), LOCK_EX);
10483                 ParseArgsFromFile(f);
10484                 fseek(f, 0, SEEK_SET);
10485                 FREE(appData.participants); appData.participants = participants;
10486                 if(expunge) { // erase results of replaced engine
10487                     int len = strlen(appData.results), w, b, dummy;
10488                     for(i=0; i<len; i++) {
10489                         Pairing(i, nPlayers, &w, &b, &dummy);
10490                         if((w == changed || b == changed) && appData.results[i] == '*') {
10491                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10492                             fclose(f);
10493                             return;
10494                         }
10495                     }
10496                     for(i=0; i<len; i++) {
10497                         Pairing(i, nPlayers, &w, &b, &dummy);
10498                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10499                     }
10500                 }
10501                 WriteTourneyFile(appData.results, f);
10502                 fclose(f); // release lock
10503                 return;
10504             }
10505         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10506     }
10507     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10508     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10509     free(participants);
10510     return;
10511 }
10512
10513 int
10514 CheckPlayers (char *participants)
10515 {
10516         int i;
10517         char buf[MSG_SIZ], *p;
10518         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10519         while(p = strchr(participants, '\n')) {
10520             *p = NULLCHAR;
10521             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10522             if(!mnemonic[i]) {
10523                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10524                 *p = '\n';
10525                 DisplayError(buf, 0);
10526                 return 1;
10527             }
10528             *p = '\n';
10529             participants = p + 1;
10530         }
10531         return 0;
10532 }
10533
10534 int
10535 CreateTourney (char *name)
10536 {
10537         FILE *f;
10538         if(matchMode && strcmp(name, appData.tourneyFile)) {
10539              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10540         }
10541         if(name[0] == NULLCHAR) {
10542             if(appData.participants[0])
10543                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10544             return 0;
10545         }
10546         f = fopen(name, "r");
10547         if(f) { // file exists
10548             ASSIGN(appData.tourneyFile, name);
10549             ParseArgsFromFile(f); // parse it
10550         } else {
10551             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10552             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10553                 DisplayError(_("Not enough participants"), 0);
10554                 return 0;
10555             }
10556             if(CheckPlayers(appData.participants)) return 0;
10557             ASSIGN(appData.tourneyFile, name);
10558             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10559             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10560         }
10561         fclose(f);
10562         appData.noChessProgram = FALSE;
10563         appData.clockMode = TRUE;
10564         SetGNUMode();
10565         return 1;
10566 }
10567
10568 int
10569 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10570 {
10571     char buf[MSG_SIZ], *p, *q;
10572     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10573     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10574     skip = !all && group[0]; // if group requested, we start in skip mode
10575     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10576         p = names; q = buf; header = 0;
10577         while(*p && *p != '\n') *q++ = *p++;
10578         *q = 0;
10579         if(*p == '\n') p++;
10580         if(buf[0] == '#') {
10581             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10582             depth++; // we must be entering a new group
10583             if(all) continue; // suppress printing group headers when complete list requested
10584             header = 1;
10585             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10586         }
10587         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10588         if(engineList[i]) free(engineList[i]);
10589         engineList[i] = strdup(buf);
10590         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10591         if(engineMnemonic[i]) free(engineMnemonic[i]);
10592         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10593             strcat(buf, " (");
10594             sscanf(q + 8, "%s", buf + strlen(buf));
10595             strcat(buf, ")");
10596         }
10597         engineMnemonic[i] = strdup(buf);
10598         i++;
10599     }
10600     engineList[i] = engineMnemonic[i] = NULL;
10601     return i;
10602 }
10603
10604 // following implemented as macro to avoid type limitations
10605 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10606
10607 void
10608 SwapEngines (int n)
10609 {   // swap settings for first engine and other engine (so far only some selected options)
10610     int h;
10611     char *p;
10612     if(n == 0) return;
10613     SWAP(directory, p)
10614     SWAP(chessProgram, p)
10615     SWAP(isUCI, h)
10616     SWAP(hasOwnBookUCI, h)
10617     SWAP(protocolVersion, h)
10618     SWAP(reuse, h)
10619     SWAP(scoreIsAbsolute, h)
10620     SWAP(timeOdds, h)
10621     SWAP(logo, p)
10622     SWAP(pgnName, p)
10623     SWAP(pvSAN, h)
10624     SWAP(engOptions, p)
10625     SWAP(engInitString, p)
10626     SWAP(computerString, p)
10627     SWAP(features, p)
10628     SWAP(fenOverride, p)
10629     SWAP(NPS, h)
10630     SWAP(accumulateTC, h)
10631     SWAP(host, p)
10632 }
10633
10634 int
10635 GetEngineLine (char *s, int n)
10636 {
10637     int i;
10638     char buf[MSG_SIZ];
10639     extern char *icsNames;
10640     if(!s || !*s) return 0;
10641     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10642     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10643     if(!mnemonic[i]) return 0;
10644     if(n == 11) return 1; // just testing if there was a match
10645     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10646     if(n == 1) SwapEngines(n);
10647     ParseArgsFromString(buf);
10648     if(n == 1) SwapEngines(n);
10649     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10650         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10651         ParseArgsFromString(buf);
10652     }
10653     return 1;
10654 }
10655
10656 int
10657 SetPlayer (int player, char *p)
10658 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10659     int i;
10660     char buf[MSG_SIZ], *engineName;
10661     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10662     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10663     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10664     if(mnemonic[i]) {
10665         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10666         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10667         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10668         ParseArgsFromString(buf);
10669     } else { // no engine with this nickname is installed!
10670         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10671         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10672         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10673         ModeHighlight();
10674         DisplayError(buf, 0);
10675         return 0;
10676     }
10677     free(engineName);
10678     return i;
10679 }
10680
10681 char *recentEngines;
10682
10683 void
10684 RecentEngineEvent (int nr)
10685 {
10686     int n;
10687 //    SwapEngines(1); // bump first to second
10688 //    ReplaceEngine(&second, 1); // and load it there
10689     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10690     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10691     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10692         ReplaceEngine(&first, 0);
10693         FloatToFront(&appData.recentEngineList, command[n]);
10694     }
10695 }
10696
10697 int
10698 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10699 {   // determine players from game number
10700     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10701
10702     if(appData.tourneyType == 0) {
10703         roundsPerCycle = (nPlayers - 1) | 1;
10704         pairingsPerRound = nPlayers / 2;
10705     } else if(appData.tourneyType > 0) {
10706         roundsPerCycle = nPlayers - appData.tourneyType;
10707         pairingsPerRound = appData.tourneyType;
10708     }
10709     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10710     gamesPerCycle = gamesPerRound * roundsPerCycle;
10711     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10712     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10713     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10714     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10715     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10716     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10717
10718     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10719     if(appData.roundSync) *syncInterval = gamesPerRound;
10720
10721     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10722
10723     if(appData.tourneyType == 0) {
10724         if(curPairing == (nPlayers-1)/2 ) {
10725             *whitePlayer = curRound;
10726             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10727         } else {
10728             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10729             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10730             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10731             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10732         }
10733     } else if(appData.tourneyType > 1) {
10734         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10735         *whitePlayer = curRound + appData.tourneyType;
10736     } else if(appData.tourneyType > 0) {
10737         *whitePlayer = curPairing;
10738         *blackPlayer = curRound + appData.tourneyType;
10739     }
10740
10741     // take care of white/black alternation per round.
10742     // For cycles and games this is already taken care of by default, derived from matchGame!
10743     return curRound & 1;
10744 }
10745
10746 int
10747 NextTourneyGame (int nr, int *swapColors)
10748 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10749     char *p, *q;
10750     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10751     FILE *tf;
10752     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10753     tf = fopen(appData.tourneyFile, "r");
10754     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10755     ParseArgsFromFile(tf); fclose(tf);
10756     InitTimeControls(); // TC might be altered from tourney file
10757
10758     nPlayers = CountPlayers(appData.participants); // count participants
10759     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10760     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10761
10762     if(syncInterval) {
10763         p = q = appData.results;
10764         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10765         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10766             DisplayMessage(_("Waiting for other game(s)"),"");
10767             waitingForGame = TRUE;
10768             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10769             return 0;
10770         }
10771         waitingForGame = FALSE;
10772     }
10773
10774     if(appData.tourneyType < 0) {
10775         if(nr>=0 && !pairingReceived) {
10776             char buf[1<<16];
10777             if(pairing.pr == NoProc) {
10778                 if(!appData.pairingEngine[0]) {
10779                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10780                     return 0;
10781                 }
10782                 StartChessProgram(&pairing); // starts the pairing engine
10783             }
10784             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10785             SendToProgram(buf, &pairing);
10786             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10787             SendToProgram(buf, &pairing);
10788             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10789         }
10790         pairingReceived = 0;                              // ... so we continue here
10791         *swapColors = 0;
10792         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10793         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10794         matchGame = 1; roundNr = nr / syncInterval + 1;
10795     }
10796
10797     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10798
10799     // redefine engines, engine dir, etc.
10800     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10801     if(first.pr == NoProc) {
10802       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10803       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10804     }
10805     if(second.pr == NoProc) {
10806       SwapEngines(1);
10807       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10808       SwapEngines(1);         // and make that valid for second engine by swapping
10809       InitEngine(&second, 1);
10810     }
10811     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10812     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10813     return OK;
10814 }
10815
10816 void
10817 NextMatchGame ()
10818 {   // performs game initialization that does not invoke engines, and then tries to start the game
10819     int res, firstWhite, swapColors = 0;
10820     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10821     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
10822         char buf[MSG_SIZ];
10823         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10824         if(strcmp(buf, currentDebugFile)) { // name has changed
10825             FILE *f = fopen(buf, "w");
10826             if(f) { // if opening the new file failed, just keep using the old one
10827                 ASSIGN(currentDebugFile, buf);
10828                 fclose(debugFP);
10829                 debugFP = f;
10830             }
10831             if(appData.serverFileName) {
10832                 if(serverFP) fclose(serverFP);
10833                 serverFP = fopen(appData.serverFileName, "w");
10834                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10835                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10836             }
10837         }
10838     }
10839     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10840     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10841     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10842     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10843     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10844     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10845     Reset(FALSE, first.pr != NoProc);
10846     res = LoadGameOrPosition(matchGame); // setup game
10847     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10848     if(!res) return; // abort when bad game/pos file
10849     TwoMachinesEvent();
10850 }
10851
10852 void
10853 UserAdjudicationEvent (int result)
10854 {
10855     ChessMove gameResult = GameIsDrawn;
10856
10857     if( result > 0 ) {
10858         gameResult = WhiteWins;
10859     }
10860     else if( result < 0 ) {
10861         gameResult = BlackWins;
10862     }
10863
10864     if( gameMode == TwoMachinesPlay ) {
10865         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10866     }
10867 }
10868
10869
10870 // [HGM] save: calculate checksum of game to make games easily identifiable
10871 int
10872 StringCheckSum (char *s)
10873 {
10874         int i = 0;
10875         if(s==NULL) return 0;
10876         while(*s) i = i*259 + *s++;
10877         return i;
10878 }
10879
10880 int
10881 GameCheckSum ()
10882 {
10883         int i, sum=0;
10884         for(i=backwardMostMove; i<forwardMostMove; i++) {
10885                 sum += pvInfoList[i].depth;
10886                 sum += StringCheckSum(parseList[i]);
10887                 sum += StringCheckSum(commentList[i]);
10888                 sum *= 261;
10889         }
10890         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10891         return sum + StringCheckSum(commentList[i]);
10892 } // end of save patch
10893
10894 void
10895 GameEnds (ChessMove result, char *resultDetails, int whosays)
10896 {
10897     GameMode nextGameMode;
10898     int isIcsGame;
10899     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10900
10901     if(endingGame) return; /* [HGM] crash: forbid recursion */
10902     endingGame = 1;
10903     if(twoBoards) { // [HGM] dual: switch back to one board
10904         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10905         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10906     }
10907     if (appData.debugMode) {
10908       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10909               result, resultDetails ? resultDetails : "(null)", whosays);
10910     }
10911
10912     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10913
10914     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10915
10916     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10917         /* If we are playing on ICS, the server decides when the
10918            game is over, but the engine can offer to draw, claim
10919            a draw, or resign.
10920          */
10921 #if ZIPPY
10922         if (appData.zippyPlay && first.initDone) {
10923             if (result == GameIsDrawn) {
10924                 /* In case draw still needs to be claimed */
10925                 SendToICS(ics_prefix);
10926                 SendToICS("draw\n");
10927             } else if (StrCaseStr(resultDetails, "resign")) {
10928                 SendToICS(ics_prefix);
10929                 SendToICS("resign\n");
10930             }
10931         }
10932 #endif
10933         endingGame = 0; /* [HGM] crash */
10934         return;
10935     }
10936
10937     /* If we're loading the game from a file, stop */
10938     if (whosays == GE_FILE) {
10939       (void) StopLoadGameTimer();
10940       gameFileFP = NULL;
10941     }
10942
10943     /* Cancel draw offers */
10944     first.offeredDraw = second.offeredDraw = 0;
10945
10946     /* If this is an ICS game, only ICS can really say it's done;
10947        if not, anyone can. */
10948     isIcsGame = (gameMode == IcsPlayingWhite ||
10949                  gameMode == IcsPlayingBlack ||
10950                  gameMode == IcsObserving    ||
10951                  gameMode == IcsExamining);
10952
10953     if (!isIcsGame || whosays == GE_ICS) {
10954         /* OK -- not an ICS game, or ICS said it was done */
10955         StopClocks();
10956         if (!isIcsGame && !appData.noChessProgram)
10957           SetUserThinkingEnables();
10958
10959         /* [HGM] if a machine claims the game end we verify this claim */
10960         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10961             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10962                 char claimer;
10963                 ChessMove trueResult = (ChessMove) -1;
10964
10965                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10966                                             first.twoMachinesColor[0] :
10967                                             second.twoMachinesColor[0] ;
10968
10969                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10970                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10971                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10972                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10973                 } else
10974                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10975                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10976                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10977                 } else
10978                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10979                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10980                 }
10981
10982                 // now verify win claims, but not in drop games, as we don't understand those yet
10983                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10984                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10985                     (result == WhiteWins && claimer == 'w' ||
10986                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10987                       if (appData.debugMode) {
10988                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10989                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10990                       }
10991                       if(result != trueResult) {
10992                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10993                               result = claimer == 'w' ? BlackWins : WhiteWins;
10994                               resultDetails = buf;
10995                       }
10996                 } else
10997                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10998                     && (forwardMostMove <= backwardMostMove ||
10999                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11000                         (claimer=='b')==(forwardMostMove&1))
11001                                                                                   ) {
11002                       /* [HGM] verify: draws that were not flagged are false claims */
11003                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11004                       result = claimer == 'w' ? BlackWins : WhiteWins;
11005                       resultDetails = buf;
11006                 }
11007                 /* (Claiming a loss is accepted no questions asked!) */
11008             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11009                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11010                 result = GameUnfinished;
11011                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11012             }
11013             /* [HGM] bare: don't allow bare King to win */
11014             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11015                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11016                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11017                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11018                && result != GameIsDrawn)
11019             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11020                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11021                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11022                         if(p >= 0 && p <= (int)WhiteKing) k++;
11023                 }
11024                 if (appData.debugMode) {
11025                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11026                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11027                 }
11028                 if(k <= 1) {
11029                         result = GameIsDrawn;
11030                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11031                         resultDetails = buf;
11032                 }
11033             }
11034         }
11035
11036
11037         if(serverMoves != NULL && !loadFlag) { char c = '=';
11038             if(result==WhiteWins) c = '+';
11039             if(result==BlackWins) c = '-';
11040             if(resultDetails != NULL)
11041                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11042         }
11043         if (resultDetails != NULL) {
11044             gameInfo.result = result;
11045             gameInfo.resultDetails = StrSave(resultDetails);
11046
11047             /* display last move only if game was not loaded from file */
11048             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11049                 DisplayMove(currentMove - 1);
11050
11051             if (forwardMostMove != 0) {
11052                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11053                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11054                                                                 ) {
11055                     if (*appData.saveGameFile != NULLCHAR) {
11056                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11057                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11058                         else
11059                         SaveGameToFile(appData.saveGameFile, TRUE);
11060                     } else if (appData.autoSaveGames) {
11061                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11062                     }
11063                     if (*appData.savePositionFile != NULLCHAR) {
11064                         SavePositionToFile(appData.savePositionFile);
11065                     }
11066                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11067                 }
11068             }
11069
11070             /* Tell program how game ended in case it is learning */
11071             /* [HGM] Moved this to after saving the PGN, just in case */
11072             /* engine died and we got here through time loss. In that */
11073             /* case we will get a fatal error writing the pipe, which */
11074             /* would otherwise lose us the PGN.                       */
11075             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11076             /* output during GameEnds should never be fatal anymore   */
11077             if (gameMode == MachinePlaysWhite ||
11078                 gameMode == MachinePlaysBlack ||
11079                 gameMode == TwoMachinesPlay ||
11080                 gameMode == IcsPlayingWhite ||
11081                 gameMode == IcsPlayingBlack ||
11082                 gameMode == BeginningOfGame) {
11083                 char buf[MSG_SIZ];
11084                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11085                         resultDetails);
11086                 if (first.pr != NoProc) {
11087                     SendToProgram(buf, &first);
11088                 }
11089                 if (second.pr != NoProc &&
11090                     gameMode == TwoMachinesPlay) {
11091                     SendToProgram(buf, &second);
11092                 }
11093             }
11094         }
11095
11096         if (appData.icsActive) {
11097             if (appData.quietPlay &&
11098                 (gameMode == IcsPlayingWhite ||
11099                  gameMode == IcsPlayingBlack)) {
11100                 SendToICS(ics_prefix);
11101                 SendToICS("set shout 1\n");
11102             }
11103             nextGameMode = IcsIdle;
11104             ics_user_moved = FALSE;
11105             /* clean up premove.  It's ugly when the game has ended and the
11106              * premove highlights are still on the board.
11107              */
11108             if (gotPremove) {
11109               gotPremove = FALSE;
11110               ClearPremoveHighlights();
11111               DrawPosition(FALSE, boards[currentMove]);
11112             }
11113             if (whosays == GE_ICS) {
11114                 switch (result) {
11115                 case WhiteWins:
11116                     if (gameMode == IcsPlayingWhite)
11117                         PlayIcsWinSound();
11118                     else if(gameMode == IcsPlayingBlack)
11119                         PlayIcsLossSound();
11120                     break;
11121                 case BlackWins:
11122                     if (gameMode == IcsPlayingBlack)
11123                         PlayIcsWinSound();
11124                     else if(gameMode == IcsPlayingWhite)
11125                         PlayIcsLossSound();
11126                     break;
11127                 case GameIsDrawn:
11128                     PlayIcsDrawSound();
11129                     break;
11130                 default:
11131                     PlayIcsUnfinishedSound();
11132                 }
11133             }
11134             if(appData.quitNext) { ExitEvent(0); return; }
11135         } else if (gameMode == EditGame ||
11136                    gameMode == PlayFromGameFile ||
11137                    gameMode == AnalyzeMode ||
11138                    gameMode == AnalyzeFile) {
11139             nextGameMode = gameMode;
11140         } else {
11141             nextGameMode = EndOfGame;
11142         }
11143         pausing = FALSE;
11144         ModeHighlight();
11145     } else {
11146         nextGameMode = gameMode;
11147     }
11148
11149     if (appData.noChessProgram) {
11150         gameMode = nextGameMode;
11151         ModeHighlight();
11152         endingGame = 0; /* [HGM] crash */
11153         return;
11154     }
11155
11156     if (first.reuse) {
11157         /* Put first chess program into idle state */
11158         if (first.pr != NoProc &&
11159             (gameMode == MachinePlaysWhite ||
11160              gameMode == MachinePlaysBlack ||
11161              gameMode == TwoMachinesPlay ||
11162              gameMode == IcsPlayingWhite ||
11163              gameMode == IcsPlayingBlack ||
11164              gameMode == BeginningOfGame)) {
11165             SendToProgram("force\n", &first);
11166             if (first.usePing) {
11167               char buf[MSG_SIZ];
11168               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11169               SendToProgram(buf, &first);
11170             }
11171         }
11172     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11173         /* Kill off first chess program */
11174         if (first.isr != NULL)
11175           RemoveInputSource(first.isr);
11176         first.isr = NULL;
11177
11178         if (first.pr != NoProc) {
11179             ExitAnalyzeMode();
11180             DoSleep( appData.delayBeforeQuit );
11181             SendToProgram("quit\n", &first);
11182             DoSleep( appData.delayAfterQuit );
11183             DestroyChildProcess(first.pr, first.useSigterm);
11184             first.reload = TRUE;
11185         }
11186         first.pr = NoProc;
11187     }
11188     if (second.reuse) {
11189         /* Put second chess program into idle state */
11190         if (second.pr != NoProc &&
11191             gameMode == TwoMachinesPlay) {
11192             SendToProgram("force\n", &second);
11193             if (second.usePing) {
11194               char buf[MSG_SIZ];
11195               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11196               SendToProgram(buf, &second);
11197             }
11198         }
11199     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11200         /* Kill off second chess program */
11201         if (second.isr != NULL)
11202           RemoveInputSource(second.isr);
11203         second.isr = NULL;
11204
11205         if (second.pr != NoProc) {
11206             DoSleep( appData.delayBeforeQuit );
11207             SendToProgram("quit\n", &second);
11208             DoSleep( appData.delayAfterQuit );
11209             DestroyChildProcess(second.pr, second.useSigterm);
11210             second.reload = TRUE;
11211         }
11212         second.pr = NoProc;
11213     }
11214
11215     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11216         char resChar = '=';
11217         switch (result) {
11218         case WhiteWins:
11219           resChar = '+';
11220           if (first.twoMachinesColor[0] == 'w') {
11221             first.matchWins++;
11222           } else {
11223             second.matchWins++;
11224           }
11225           break;
11226         case BlackWins:
11227           resChar = '-';
11228           if (first.twoMachinesColor[0] == 'b') {
11229             first.matchWins++;
11230           } else {
11231             second.matchWins++;
11232           }
11233           break;
11234         case GameUnfinished:
11235           resChar = ' ';
11236         default:
11237           break;
11238         }
11239
11240         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11241         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11242             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11243             ReserveGame(nextGame, resChar); // sets nextGame
11244             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11245             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11246         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11247
11248         if (nextGame <= appData.matchGames && !abortMatch) {
11249             gameMode = nextGameMode;
11250             matchGame = nextGame; // this will be overruled in tourney mode!
11251             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11252             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11253             endingGame = 0; /* [HGM] crash */
11254             return;
11255         } else {
11256             gameMode = nextGameMode;
11257             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11258                      first.tidy, second.tidy,
11259                      first.matchWins, second.matchWins,
11260                      appData.matchGames - (first.matchWins + second.matchWins));
11261             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11262             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11263             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11264             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11265                 first.twoMachinesColor = "black\n";
11266                 second.twoMachinesColor = "white\n";
11267             } else {
11268                 first.twoMachinesColor = "white\n";
11269                 second.twoMachinesColor = "black\n";
11270             }
11271         }
11272     }
11273     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11274         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11275       ExitAnalyzeMode();
11276     gameMode = nextGameMode;
11277     ModeHighlight();
11278     endingGame = 0;  /* [HGM] crash */
11279     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11280         if(matchMode == TRUE) { // match through command line: exit with or without popup
11281             if(ranking) {
11282                 ToNrEvent(forwardMostMove);
11283                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11284                 else ExitEvent(0);
11285             } else DisplayFatalError(buf, 0, 0);
11286         } else { // match through menu; just stop, with or without popup
11287             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11288             ModeHighlight();
11289             if(ranking){
11290                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11291             } else DisplayNote(buf);
11292       }
11293       if(ranking) free(ranking);
11294     }
11295 }
11296
11297 /* Assumes program was just initialized (initString sent).
11298    Leaves program in force mode. */
11299 void
11300 FeedMovesToProgram (ChessProgramState *cps, int upto)
11301 {
11302     int i;
11303
11304     if (appData.debugMode)
11305       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11306               startedFromSetupPosition ? "position and " : "",
11307               backwardMostMove, upto, cps->which);
11308     if(currentlyInitializedVariant != gameInfo.variant) {
11309       char buf[MSG_SIZ];
11310         // [HGM] variantswitch: make engine aware of new variant
11311         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11312                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11313         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11314         SendToProgram(buf, cps);
11315         currentlyInitializedVariant = gameInfo.variant;
11316     }
11317     SendToProgram("force\n", cps);
11318     if (startedFromSetupPosition) {
11319         SendBoard(cps, backwardMostMove);
11320     if (appData.debugMode) {
11321         fprintf(debugFP, "feedMoves\n");
11322     }
11323     }
11324     for (i = backwardMostMove; i < upto; i++) {
11325         SendMoveToProgram(i, cps);
11326     }
11327 }
11328
11329
11330 int
11331 ResurrectChessProgram ()
11332 {
11333      /* The chess program may have exited.
11334         If so, restart it and feed it all the moves made so far. */
11335     static int doInit = 0;
11336
11337     if (appData.noChessProgram) return 1;
11338
11339     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11340         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11341         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11342         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11343     } else {
11344         if (first.pr != NoProc) return 1;
11345         StartChessProgram(&first);
11346     }
11347     InitChessProgram(&first, FALSE);
11348     FeedMovesToProgram(&first, currentMove);
11349
11350     if (!first.sendTime) {
11351         /* can't tell gnuchess what its clock should read,
11352            so we bow to its notion. */
11353         ResetClocks();
11354         timeRemaining[0][currentMove] = whiteTimeRemaining;
11355         timeRemaining[1][currentMove] = blackTimeRemaining;
11356     }
11357
11358     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11359                 appData.icsEngineAnalyze) && first.analysisSupport) {
11360       SendToProgram("analyze\n", &first);
11361       first.analyzing = TRUE;
11362     }
11363     return 1;
11364 }
11365
11366 /*
11367  * Button procedures
11368  */
11369 void
11370 Reset (int redraw, int init)
11371 {
11372     int i;
11373
11374     if (appData.debugMode) {
11375         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11376                 redraw, init, gameMode);
11377     }
11378     CleanupTail(); // [HGM] vari: delete any stored variations
11379     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11380     pausing = pauseExamInvalid = FALSE;
11381     startedFromSetupPosition = blackPlaysFirst = FALSE;
11382     firstMove = TRUE;
11383     whiteFlag = blackFlag = FALSE;
11384     userOfferedDraw = FALSE;
11385     hintRequested = bookRequested = FALSE;
11386     first.maybeThinking = FALSE;
11387     second.maybeThinking = FALSE;
11388     first.bookSuspend = FALSE; // [HGM] book
11389     second.bookSuspend = FALSE;
11390     thinkOutput[0] = NULLCHAR;
11391     lastHint[0] = NULLCHAR;
11392     ClearGameInfo(&gameInfo);
11393     gameInfo.variant = StringToVariant(appData.variant);
11394     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11395     ics_user_moved = ics_clock_paused = FALSE;
11396     ics_getting_history = H_FALSE;
11397     ics_gamenum = -1;
11398     white_holding[0] = black_holding[0] = NULLCHAR;
11399     ClearProgramStats();
11400     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11401
11402     ResetFrontEnd();
11403     ClearHighlights();
11404     flipView = appData.flipView;
11405     ClearPremoveHighlights();
11406     gotPremove = FALSE;
11407     alarmSounded = FALSE;
11408     killX = killY = -1; // [HGM] lion
11409
11410     GameEnds(EndOfFile, NULL, GE_PLAYER);
11411     if(appData.serverMovesName != NULL) {
11412         /* [HGM] prepare to make moves file for broadcasting */
11413         clock_t t = clock();
11414         if(serverMoves != NULL) fclose(serverMoves);
11415         serverMoves = fopen(appData.serverMovesName, "r");
11416         if(serverMoves != NULL) {
11417             fclose(serverMoves);
11418             /* delay 15 sec before overwriting, so all clients can see end */
11419             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11420         }
11421         serverMoves = fopen(appData.serverMovesName, "w");
11422     }
11423
11424     ExitAnalyzeMode();
11425     gameMode = BeginningOfGame;
11426     ModeHighlight();
11427     if(appData.icsActive) gameInfo.variant = VariantNormal;
11428     currentMove = forwardMostMove = backwardMostMove = 0;
11429     MarkTargetSquares(1);
11430     InitPosition(redraw);
11431     for (i = 0; i < MAX_MOVES; i++) {
11432         if (commentList[i] != NULL) {
11433             free(commentList[i]);
11434             commentList[i] = NULL;
11435         }
11436     }
11437     ResetClocks();
11438     timeRemaining[0][0] = whiteTimeRemaining;
11439     timeRemaining[1][0] = blackTimeRemaining;
11440
11441     if (first.pr == NoProc) {
11442         StartChessProgram(&first);
11443     }
11444     if (init) {
11445             InitChessProgram(&first, startedFromSetupPosition);
11446     }
11447     DisplayTitle("");
11448     DisplayMessage("", "");
11449     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11450     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11451     ClearMap();        // [HGM] exclude: invalidate map
11452 }
11453
11454 void
11455 AutoPlayGameLoop ()
11456 {
11457     for (;;) {
11458         if (!AutoPlayOneMove())
11459           return;
11460         if (matchMode || appData.timeDelay == 0)
11461           continue;
11462         if (appData.timeDelay < 0)
11463           return;
11464         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11465         break;
11466     }
11467 }
11468
11469 void
11470 AnalyzeNextGame()
11471 {
11472     ReloadGame(1); // next game
11473 }
11474
11475 int
11476 AutoPlayOneMove ()
11477 {
11478     int fromX, fromY, toX, toY;
11479
11480     if (appData.debugMode) {
11481       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11482     }
11483
11484     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11485       return FALSE;
11486
11487     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11488       pvInfoList[currentMove].depth = programStats.depth;
11489       pvInfoList[currentMove].score = programStats.score;
11490       pvInfoList[currentMove].time  = 0;
11491       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11492       else { // append analysis of final position as comment
11493         char buf[MSG_SIZ];
11494         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11495         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11496       }
11497       programStats.depth = 0;
11498     }
11499
11500     if (currentMove >= forwardMostMove) {
11501       if(gameMode == AnalyzeFile) {
11502           if(appData.loadGameIndex == -1) {
11503             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11504           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11505           } else {
11506           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11507         }
11508       }
11509 //      gameMode = EndOfGame;
11510 //      ModeHighlight();
11511
11512       /* [AS] Clear current move marker at the end of a game */
11513       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11514
11515       return FALSE;
11516     }
11517
11518     toX = moveList[currentMove][2] - AAA;
11519     toY = moveList[currentMove][3] - ONE;
11520
11521     if (moveList[currentMove][1] == '@') {
11522         if (appData.highlightLastMove) {
11523             SetHighlights(-1, -1, toX, toY);
11524         }
11525     } else {
11526         fromX = moveList[currentMove][0] - AAA;
11527         fromY = moveList[currentMove][1] - ONE;
11528
11529         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11530
11531         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11532
11533         if (appData.highlightLastMove) {
11534             SetHighlights(fromX, fromY, toX, toY);
11535         }
11536     }
11537     DisplayMove(currentMove);
11538     SendMoveToProgram(currentMove++, &first);
11539     DisplayBothClocks();
11540     DrawPosition(FALSE, boards[currentMove]);
11541     // [HGM] PV info: always display, routine tests if empty
11542     DisplayComment(currentMove - 1, commentList[currentMove]);
11543     return TRUE;
11544 }
11545
11546
11547 int
11548 LoadGameOneMove (ChessMove readAhead)
11549 {
11550     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11551     char promoChar = NULLCHAR;
11552     ChessMove moveType;
11553     char move[MSG_SIZ];
11554     char *p, *q;
11555
11556     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11557         gameMode != AnalyzeMode && gameMode != Training) {
11558         gameFileFP = NULL;
11559         return FALSE;
11560     }
11561
11562     yyboardindex = forwardMostMove;
11563     if (readAhead != EndOfFile) {
11564       moveType = readAhead;
11565     } else {
11566       if (gameFileFP == NULL)
11567           return FALSE;
11568       moveType = (ChessMove) Myylex();
11569     }
11570
11571     done = FALSE;
11572     switch (moveType) {
11573       case Comment:
11574         if (appData.debugMode)
11575           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11576         p = yy_text;
11577
11578         /* append the comment but don't display it */
11579         AppendComment(currentMove, p, FALSE);
11580         return TRUE;
11581
11582       case WhiteCapturesEnPassant:
11583       case BlackCapturesEnPassant:
11584       case WhitePromotion:
11585       case BlackPromotion:
11586       case WhiteNonPromotion:
11587       case BlackNonPromotion:
11588       case NormalMove:
11589       case FirstLeg:
11590       case WhiteKingSideCastle:
11591       case WhiteQueenSideCastle:
11592       case BlackKingSideCastle:
11593       case BlackQueenSideCastle:
11594       case WhiteKingSideCastleWild:
11595       case WhiteQueenSideCastleWild:
11596       case BlackKingSideCastleWild:
11597       case BlackQueenSideCastleWild:
11598       /* PUSH Fabien */
11599       case WhiteHSideCastleFR:
11600       case WhiteASideCastleFR:
11601       case BlackHSideCastleFR:
11602       case BlackASideCastleFR:
11603       /* POP Fabien */
11604         if (appData.debugMode)
11605           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11606         fromX = currentMoveString[0] - AAA;
11607         fromY = currentMoveString[1] - ONE;
11608         toX = currentMoveString[2] - AAA;
11609         toY = currentMoveString[3] - ONE;
11610         promoChar = currentMoveString[4];
11611         if(promoChar == ';') promoChar = NULLCHAR;
11612         break;
11613
11614       case WhiteDrop:
11615       case BlackDrop:
11616         if (appData.debugMode)
11617           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11618         fromX = moveType == WhiteDrop ?
11619           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11620         (int) CharToPiece(ToLower(currentMoveString[0]));
11621         fromY = DROP_RANK;
11622         toX = currentMoveString[2] - AAA;
11623         toY = currentMoveString[3] - ONE;
11624         break;
11625
11626       case WhiteWins:
11627       case BlackWins:
11628       case GameIsDrawn:
11629       case GameUnfinished:
11630         if (appData.debugMode)
11631           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11632         p = strchr(yy_text, '{');
11633         if (p == NULL) p = strchr(yy_text, '(');
11634         if (p == NULL) {
11635             p = yy_text;
11636             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11637         } else {
11638             q = strchr(p, *p == '{' ? '}' : ')');
11639             if (q != NULL) *q = NULLCHAR;
11640             p++;
11641         }
11642         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11643         GameEnds(moveType, p, GE_FILE);
11644         done = TRUE;
11645         if (cmailMsgLoaded) {
11646             ClearHighlights();
11647             flipView = WhiteOnMove(currentMove);
11648             if (moveType == GameUnfinished) flipView = !flipView;
11649             if (appData.debugMode)
11650               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11651         }
11652         break;
11653
11654       case EndOfFile:
11655         if (appData.debugMode)
11656           fprintf(debugFP, "Parser hit end of file\n");
11657         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11658           case MT_NONE:
11659           case MT_CHECK:
11660             break;
11661           case MT_CHECKMATE:
11662           case MT_STAINMATE:
11663             if (WhiteOnMove(currentMove)) {
11664                 GameEnds(BlackWins, "Black mates", GE_FILE);
11665             } else {
11666                 GameEnds(WhiteWins, "White mates", GE_FILE);
11667             }
11668             break;
11669           case MT_STALEMATE:
11670             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11671             break;
11672         }
11673         done = TRUE;
11674         break;
11675
11676       case MoveNumberOne:
11677         if (lastLoadGameStart == GNUChessGame) {
11678             /* GNUChessGames have numbers, but they aren't move numbers */
11679             if (appData.debugMode)
11680               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11681                       yy_text, (int) moveType);
11682             return LoadGameOneMove(EndOfFile); /* tail recursion */
11683         }
11684         /* else fall thru */
11685
11686       case XBoardGame:
11687       case GNUChessGame:
11688       case PGNTag:
11689         /* Reached start of next game in file */
11690         if (appData.debugMode)
11691           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11692         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11693           case MT_NONE:
11694           case MT_CHECK:
11695             break;
11696           case MT_CHECKMATE:
11697           case MT_STAINMATE:
11698             if (WhiteOnMove(currentMove)) {
11699                 GameEnds(BlackWins, "Black mates", GE_FILE);
11700             } else {
11701                 GameEnds(WhiteWins, "White mates", GE_FILE);
11702             }
11703             break;
11704           case MT_STALEMATE:
11705             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11706             break;
11707         }
11708         done = TRUE;
11709         break;
11710
11711       case PositionDiagram:     /* should not happen; ignore */
11712       case ElapsedTime:         /* ignore */
11713       case NAG:                 /* ignore */
11714         if (appData.debugMode)
11715           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11716                   yy_text, (int) moveType);
11717         return LoadGameOneMove(EndOfFile); /* tail recursion */
11718
11719       case IllegalMove:
11720         if (appData.testLegality) {
11721             if (appData.debugMode)
11722               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11723             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11724                     (forwardMostMove / 2) + 1,
11725                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11726             DisplayError(move, 0);
11727             done = TRUE;
11728         } else {
11729             if (appData.debugMode)
11730               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11731                       yy_text, currentMoveString);
11732             fromX = currentMoveString[0] - AAA;
11733             fromY = currentMoveString[1] - ONE;
11734             toX = currentMoveString[2] - AAA;
11735             toY = currentMoveString[3] - ONE;
11736             promoChar = currentMoveString[4];
11737         }
11738         break;
11739
11740       case AmbiguousMove:
11741         if (appData.debugMode)
11742           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11743         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11744                 (forwardMostMove / 2) + 1,
11745                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11746         DisplayError(move, 0);
11747         done = TRUE;
11748         break;
11749
11750       default:
11751       case ImpossibleMove:
11752         if (appData.debugMode)
11753           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11754         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11755                 (forwardMostMove / 2) + 1,
11756                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11757         DisplayError(move, 0);
11758         done = TRUE;
11759         break;
11760     }
11761
11762     if (done) {
11763         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11764             DrawPosition(FALSE, boards[currentMove]);
11765             DisplayBothClocks();
11766             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11767               DisplayComment(currentMove - 1, commentList[currentMove]);
11768         }
11769         (void) StopLoadGameTimer();
11770         gameFileFP = NULL;
11771         cmailOldMove = forwardMostMove;
11772         return FALSE;
11773     } else {
11774         /* currentMoveString is set as a side-effect of yylex */
11775
11776         thinkOutput[0] = NULLCHAR;
11777         MakeMove(fromX, fromY, toX, toY, promoChar);
11778         killX = killY = -1; // [HGM] lion: used up
11779         currentMove = forwardMostMove;
11780         return TRUE;
11781     }
11782 }
11783
11784 /* Load the nth game from the given file */
11785 int
11786 LoadGameFromFile (char *filename, int n, char *title, int useList)
11787 {
11788     FILE *f;
11789     char buf[MSG_SIZ];
11790
11791     if (strcmp(filename, "-") == 0) {
11792         f = stdin;
11793         title = "stdin";
11794     } else {
11795         f = fopen(filename, "rb");
11796         if (f == NULL) {
11797           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11798             DisplayError(buf, errno);
11799             return FALSE;
11800         }
11801     }
11802     if (fseek(f, 0, 0) == -1) {
11803         /* f is not seekable; probably a pipe */
11804         useList = FALSE;
11805     }
11806     if (useList && n == 0) {
11807         int error = GameListBuild(f);
11808         if (error) {
11809             DisplayError(_("Cannot build game list"), error);
11810         } else if (!ListEmpty(&gameList) &&
11811                    ((ListGame *) gameList.tailPred)->number > 1) {
11812             GameListPopUp(f, title);
11813             return TRUE;
11814         }
11815         GameListDestroy();
11816         n = 1;
11817     }
11818     if (n == 0) n = 1;
11819     return LoadGame(f, n, title, FALSE);
11820 }
11821
11822
11823 void
11824 MakeRegisteredMove ()
11825 {
11826     int fromX, fromY, toX, toY;
11827     char promoChar;
11828     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11829         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11830           case CMAIL_MOVE:
11831           case CMAIL_DRAW:
11832             if (appData.debugMode)
11833               fprintf(debugFP, "Restoring %s for game %d\n",
11834                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11835
11836             thinkOutput[0] = NULLCHAR;
11837             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11838             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11839             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11840             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11841             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11842             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11843             MakeMove(fromX, fromY, toX, toY, promoChar);
11844             ShowMove(fromX, fromY, toX, toY);
11845
11846             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11847               case MT_NONE:
11848               case MT_CHECK:
11849                 break;
11850
11851               case MT_CHECKMATE:
11852               case MT_STAINMATE:
11853                 if (WhiteOnMove(currentMove)) {
11854                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11855                 } else {
11856                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11857                 }
11858                 break;
11859
11860               case MT_STALEMATE:
11861                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11862                 break;
11863             }
11864
11865             break;
11866
11867           case CMAIL_RESIGN:
11868             if (WhiteOnMove(currentMove)) {
11869                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11870             } else {
11871                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11872             }
11873             break;
11874
11875           case CMAIL_ACCEPT:
11876             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11877             break;
11878
11879           default:
11880             break;
11881         }
11882     }
11883
11884     return;
11885 }
11886
11887 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11888 int
11889 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11890 {
11891     int retVal;
11892
11893     if (gameNumber > nCmailGames) {
11894         DisplayError(_("No more games in this message"), 0);
11895         return FALSE;
11896     }
11897     if (f == lastLoadGameFP) {
11898         int offset = gameNumber - lastLoadGameNumber;
11899         if (offset == 0) {
11900             cmailMsg[0] = NULLCHAR;
11901             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11902                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11903                 nCmailMovesRegistered--;
11904             }
11905             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11906             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11907                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11908             }
11909         } else {
11910             if (! RegisterMove()) return FALSE;
11911         }
11912     }
11913
11914     retVal = LoadGame(f, gameNumber, title, useList);
11915
11916     /* Make move registered during previous look at this game, if any */
11917     MakeRegisteredMove();
11918
11919     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11920         commentList[currentMove]
11921           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11922         DisplayComment(currentMove - 1, commentList[currentMove]);
11923     }
11924
11925     return retVal;
11926 }
11927
11928 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11929 int
11930 ReloadGame (int offset)
11931 {
11932     int gameNumber = lastLoadGameNumber + offset;
11933     if (lastLoadGameFP == NULL) {
11934         DisplayError(_("No game has been loaded yet"), 0);
11935         return FALSE;
11936     }
11937     if (gameNumber <= 0) {
11938         DisplayError(_("Can't back up any further"), 0);
11939         return FALSE;
11940     }
11941     if (cmailMsgLoaded) {
11942         return CmailLoadGame(lastLoadGameFP, gameNumber,
11943                              lastLoadGameTitle, lastLoadGameUseList);
11944     } else {
11945         return LoadGame(lastLoadGameFP, gameNumber,
11946                         lastLoadGameTitle, lastLoadGameUseList);
11947     }
11948 }
11949
11950 int keys[EmptySquare+1];
11951
11952 int
11953 PositionMatches (Board b1, Board b2)
11954 {
11955     int r, f, sum=0;
11956     switch(appData.searchMode) {
11957         case 1: return CompareWithRights(b1, b2);
11958         case 2:
11959             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11960                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11961             }
11962             return TRUE;
11963         case 3:
11964             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11965               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11966                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11967             }
11968             return sum==0;
11969         case 4:
11970             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11971                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11972             }
11973             return sum==0;
11974     }
11975     return TRUE;
11976 }
11977
11978 #define Q_PROMO  4
11979 #define Q_EP     3
11980 #define Q_BCASTL 2
11981 #define Q_WCASTL 1
11982
11983 int pieceList[256], quickBoard[256];
11984 ChessSquare pieceType[256] = { EmptySquare };
11985 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11986 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11987 int soughtTotal, turn;
11988 Boolean epOK, flipSearch;
11989
11990 typedef struct {
11991     unsigned char piece, to;
11992 } Move;
11993
11994 #define DSIZE (250000)
11995
11996 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11997 Move *moveDatabase = initialSpace;
11998 unsigned int movePtr, dataSize = DSIZE;
11999
12000 int
12001 MakePieceList (Board board, int *counts)
12002 {
12003     int r, f, n=Q_PROMO, total=0;
12004     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12005     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12006         int sq = f + (r<<4);
12007         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12008             quickBoard[sq] = ++n;
12009             pieceList[n] = sq;
12010             pieceType[n] = board[r][f];
12011             counts[board[r][f]]++;
12012             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12013             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12014             total++;
12015         }
12016     }
12017     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12018     return total;
12019 }
12020
12021 void
12022 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12023 {
12024     int sq = fromX + (fromY<<4);
12025     int piece = quickBoard[sq];
12026     quickBoard[sq] = 0;
12027     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12028     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12029         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12030         moveDatabase[movePtr++].piece = Q_WCASTL;
12031         quickBoard[sq] = piece;
12032         piece = quickBoard[from]; quickBoard[from] = 0;
12033         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12034     } else
12035     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12036         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12037         moveDatabase[movePtr++].piece = Q_BCASTL;
12038         quickBoard[sq] = piece;
12039         piece = quickBoard[from]; quickBoard[from] = 0;
12040         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12041     } else
12042     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12043         quickBoard[(fromY<<4)+toX] = 0;
12044         moveDatabase[movePtr].piece = Q_EP;
12045         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12046         moveDatabase[movePtr].to = sq;
12047     } else
12048     if(promoPiece != pieceType[piece]) {
12049         moveDatabase[movePtr++].piece = Q_PROMO;
12050         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12051     }
12052     moveDatabase[movePtr].piece = piece;
12053     quickBoard[sq] = piece;
12054     movePtr++;
12055 }
12056
12057 int
12058 PackGame (Board board)
12059 {
12060     Move *newSpace = NULL;
12061     moveDatabase[movePtr].piece = 0; // terminate previous game
12062     if(movePtr > dataSize) {
12063         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12064         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12065         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12066         if(newSpace) {
12067             int i;
12068             Move *p = moveDatabase, *q = newSpace;
12069             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12070             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12071             moveDatabase = newSpace;
12072         } else { // calloc failed, we must be out of memory. Too bad...
12073             dataSize = 0; // prevent calloc events for all subsequent games
12074             return 0;     // and signal this one isn't cached
12075         }
12076     }
12077     movePtr++;
12078     MakePieceList(board, counts);
12079     return movePtr;
12080 }
12081
12082 int
12083 QuickCompare (Board board, int *minCounts, int *maxCounts)
12084 {   // compare according to search mode
12085     int r, f;
12086     switch(appData.searchMode)
12087     {
12088       case 1: // exact position match
12089         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12090         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12091             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12092         }
12093         break;
12094       case 2: // can have extra material on empty squares
12095         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12096             if(board[r][f] == EmptySquare) continue;
12097             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12098         }
12099         break;
12100       case 3: // material with exact Pawn structure
12101         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12102             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12103             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12104         } // fall through to material comparison
12105       case 4: // exact material
12106         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12107         break;
12108       case 6: // material range with given imbalance
12109         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12110         // fall through to range comparison
12111       case 5: // material range
12112         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12113     }
12114     return TRUE;
12115 }
12116
12117 int
12118 QuickScan (Board board, Move *move)
12119 {   // reconstruct game,and compare all positions in it
12120     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12121     do {
12122         int piece = move->piece;
12123         int to = move->to, from = pieceList[piece];
12124         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12125           if(!piece) return -1;
12126           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12127             piece = (++move)->piece;
12128             from = pieceList[piece];
12129             counts[pieceType[piece]]--;
12130             pieceType[piece] = (ChessSquare) move->to;
12131             counts[move->to]++;
12132           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12133             counts[pieceType[quickBoard[to]]]--;
12134             quickBoard[to] = 0; total--;
12135             move++;
12136             continue;
12137           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12138             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12139             from  = pieceList[piece]; // so this must be King
12140             quickBoard[from] = 0;
12141             pieceList[piece] = to;
12142             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12143             quickBoard[from] = 0; // rook
12144             quickBoard[to] = piece;
12145             to = move->to; piece = move->piece;
12146             goto aftercastle;
12147           }
12148         }
12149         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12150         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12151         quickBoard[from] = 0;
12152       aftercastle:
12153         quickBoard[to] = piece;
12154         pieceList[piece] = to;
12155         cnt++; turn ^= 3;
12156         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12157            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12158            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12159                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12160           ) {
12161             static int lastCounts[EmptySquare+1];
12162             int i;
12163             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12164             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12165         } else stretch = 0;
12166         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12167         move++;
12168     } while(1);
12169 }
12170
12171 void
12172 InitSearch ()
12173 {
12174     int r, f;
12175     flipSearch = FALSE;
12176     CopyBoard(soughtBoard, boards[currentMove]);
12177     soughtTotal = MakePieceList(soughtBoard, maxSought);
12178     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12179     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12180     CopyBoard(reverseBoard, boards[currentMove]);
12181     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12182         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12183         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12184         reverseBoard[r][f] = piece;
12185     }
12186     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12187     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12188     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12189                  || (boards[currentMove][CASTLING][2] == NoRights ||
12190                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12191                  && (boards[currentMove][CASTLING][5] == NoRights ||
12192                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12193       ) {
12194         flipSearch = TRUE;
12195         CopyBoard(flipBoard, soughtBoard);
12196         CopyBoard(rotateBoard, reverseBoard);
12197         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12198             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12199             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12200         }
12201     }
12202     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12203     if(appData.searchMode >= 5) {
12204         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12205         MakePieceList(soughtBoard, minSought);
12206         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12207     }
12208     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12209         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12210 }
12211
12212 GameInfo dummyInfo;
12213 static int creatingBook;
12214
12215 int
12216 GameContainsPosition (FILE *f, ListGame *lg)
12217 {
12218     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12219     int fromX, fromY, toX, toY;
12220     char promoChar;
12221     static int initDone=FALSE;
12222
12223     // weed out games based on numerical tag comparison
12224     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12225     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12226     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12227     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12228     if(!initDone) {
12229         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12230         initDone = TRUE;
12231     }
12232     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12233     else CopyBoard(boards[scratch], initialPosition); // default start position
12234     if(lg->moves) {
12235         turn = btm + 1;
12236         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12237         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12238     }
12239     if(btm) plyNr++;
12240     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12241     fseek(f, lg->offset, 0);
12242     yynewfile(f);
12243     while(1) {
12244         yyboardindex = scratch;
12245         quickFlag = plyNr+1;
12246         next = Myylex();
12247         quickFlag = 0;
12248         switch(next) {
12249             case PGNTag:
12250                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12251             default:
12252                 continue;
12253
12254             case XBoardGame:
12255             case GNUChessGame:
12256                 if(plyNr) return -1; // after we have seen moves, this is for new game
12257               continue;
12258
12259             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12260             case ImpossibleMove:
12261             case WhiteWins: // game ends here with these four
12262             case BlackWins:
12263             case GameIsDrawn:
12264             case GameUnfinished:
12265                 return -1;
12266
12267             case IllegalMove:
12268                 if(appData.testLegality) return -1;
12269             case WhiteCapturesEnPassant:
12270             case BlackCapturesEnPassant:
12271             case WhitePromotion:
12272             case BlackPromotion:
12273             case WhiteNonPromotion:
12274             case BlackNonPromotion:
12275             case NormalMove:
12276             case FirstLeg:
12277             case WhiteKingSideCastle:
12278             case WhiteQueenSideCastle:
12279             case BlackKingSideCastle:
12280             case BlackQueenSideCastle:
12281             case WhiteKingSideCastleWild:
12282             case WhiteQueenSideCastleWild:
12283             case BlackKingSideCastleWild:
12284             case BlackQueenSideCastleWild:
12285             case WhiteHSideCastleFR:
12286             case WhiteASideCastleFR:
12287             case BlackHSideCastleFR:
12288             case BlackASideCastleFR:
12289                 fromX = currentMoveString[0] - AAA;
12290                 fromY = currentMoveString[1] - ONE;
12291                 toX = currentMoveString[2] - AAA;
12292                 toY = currentMoveString[3] - ONE;
12293                 promoChar = currentMoveString[4];
12294                 break;
12295             case WhiteDrop:
12296             case BlackDrop:
12297                 fromX = next == WhiteDrop ?
12298                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12299                   (int) CharToPiece(ToLower(currentMoveString[0]));
12300                 fromY = DROP_RANK;
12301                 toX = currentMoveString[2] - AAA;
12302                 toY = currentMoveString[3] - ONE;
12303                 promoChar = 0;
12304                 break;
12305         }
12306         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12307         plyNr++;
12308         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12309         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12310         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12311         if(appData.findMirror) {
12312             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12313             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12314         }
12315     }
12316 }
12317
12318 /* Load the nth game from open file f */
12319 int
12320 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12321 {
12322     ChessMove cm;
12323     char buf[MSG_SIZ];
12324     int gn = gameNumber;
12325     ListGame *lg = NULL;
12326     int numPGNTags = 0;
12327     int err, pos = -1;
12328     GameMode oldGameMode;
12329     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12330
12331     if (appData.debugMode)
12332         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12333
12334     if (gameMode == Training )
12335         SetTrainingModeOff();
12336
12337     oldGameMode = gameMode;
12338     if (gameMode != BeginningOfGame) {
12339       Reset(FALSE, TRUE);
12340     }
12341     killX = killY = -1; // [HGM] lion: in case we did not Reset
12342
12343     gameFileFP = f;
12344     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12345         fclose(lastLoadGameFP);
12346     }
12347
12348     if (useList) {
12349         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12350
12351         if (lg) {
12352             fseek(f, lg->offset, 0);
12353             GameListHighlight(gameNumber);
12354             pos = lg->position;
12355             gn = 1;
12356         }
12357         else {
12358             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12359               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12360             else
12361             DisplayError(_("Game number out of range"), 0);
12362             return FALSE;
12363         }
12364     } else {
12365         GameListDestroy();
12366         if (fseek(f, 0, 0) == -1) {
12367             if (f == lastLoadGameFP ?
12368                 gameNumber == lastLoadGameNumber + 1 :
12369                 gameNumber == 1) {
12370                 gn = 1;
12371             } else {
12372                 DisplayError(_("Can't seek on game file"), 0);
12373                 return FALSE;
12374             }
12375         }
12376     }
12377     lastLoadGameFP = f;
12378     lastLoadGameNumber = gameNumber;
12379     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12380     lastLoadGameUseList = useList;
12381
12382     yynewfile(f);
12383
12384     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12385       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12386                 lg->gameInfo.black);
12387             DisplayTitle(buf);
12388     } else if (*title != NULLCHAR) {
12389         if (gameNumber > 1) {
12390           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12391             DisplayTitle(buf);
12392         } else {
12393             DisplayTitle(title);
12394         }
12395     }
12396
12397     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12398         gameMode = PlayFromGameFile;
12399         ModeHighlight();
12400     }
12401
12402     currentMove = forwardMostMove = backwardMostMove = 0;
12403     CopyBoard(boards[0], initialPosition);
12404     StopClocks();
12405
12406     /*
12407      * Skip the first gn-1 games in the file.
12408      * Also skip over anything that precedes an identifiable
12409      * start of game marker, to avoid being confused by
12410      * garbage at the start of the file.  Currently
12411      * recognized start of game markers are the move number "1",
12412      * the pattern "gnuchess .* game", the pattern
12413      * "^[#;%] [^ ]* game file", and a PGN tag block.
12414      * A game that starts with one of the latter two patterns
12415      * will also have a move number 1, possibly
12416      * following a position diagram.
12417      * 5-4-02: Let's try being more lenient and allowing a game to
12418      * start with an unnumbered move.  Does that break anything?
12419      */
12420     cm = lastLoadGameStart = EndOfFile;
12421     while (gn > 0) {
12422         yyboardindex = forwardMostMove;
12423         cm = (ChessMove) Myylex();
12424         switch (cm) {
12425           case EndOfFile:
12426             if (cmailMsgLoaded) {
12427                 nCmailGames = CMAIL_MAX_GAMES - gn;
12428             } else {
12429                 Reset(TRUE, TRUE);
12430                 DisplayError(_("Game not found in file"), 0);
12431             }
12432             return FALSE;
12433
12434           case GNUChessGame:
12435           case XBoardGame:
12436             gn--;
12437             lastLoadGameStart = cm;
12438             break;
12439
12440           case MoveNumberOne:
12441             switch (lastLoadGameStart) {
12442               case GNUChessGame:
12443               case XBoardGame:
12444               case PGNTag:
12445                 break;
12446               case MoveNumberOne:
12447               case EndOfFile:
12448                 gn--;           /* count this game */
12449                 lastLoadGameStart = cm;
12450                 break;
12451               default:
12452                 /* impossible */
12453                 break;
12454             }
12455             break;
12456
12457           case PGNTag:
12458             switch (lastLoadGameStart) {
12459               case GNUChessGame:
12460               case PGNTag:
12461               case MoveNumberOne:
12462               case EndOfFile:
12463                 gn--;           /* count this game */
12464                 lastLoadGameStart = cm;
12465                 break;
12466               case XBoardGame:
12467                 lastLoadGameStart = cm; /* game counted already */
12468                 break;
12469               default:
12470                 /* impossible */
12471                 break;
12472             }
12473             if (gn > 0) {
12474                 do {
12475                     yyboardindex = forwardMostMove;
12476                     cm = (ChessMove) Myylex();
12477                 } while (cm == PGNTag || cm == Comment);
12478             }
12479             break;
12480
12481           case WhiteWins:
12482           case BlackWins:
12483           case GameIsDrawn:
12484             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12485                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12486                     != CMAIL_OLD_RESULT) {
12487                     nCmailResults ++ ;
12488                     cmailResult[  CMAIL_MAX_GAMES
12489                                 - gn - 1] = CMAIL_OLD_RESULT;
12490                 }
12491             }
12492             break;
12493
12494           case NormalMove:
12495           case FirstLeg:
12496             /* Only a NormalMove can be at the start of a game
12497              * without a position diagram. */
12498             if (lastLoadGameStart == EndOfFile ) {
12499               gn--;
12500               lastLoadGameStart = MoveNumberOne;
12501             }
12502             break;
12503
12504           default:
12505             break;
12506         }
12507     }
12508
12509     if (appData.debugMode)
12510       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12511
12512     if (cm == XBoardGame) {
12513         /* Skip any header junk before position diagram and/or move 1 */
12514         for (;;) {
12515             yyboardindex = forwardMostMove;
12516             cm = (ChessMove) Myylex();
12517
12518             if (cm == EndOfFile ||
12519                 cm == GNUChessGame || cm == XBoardGame) {
12520                 /* Empty game; pretend end-of-file and handle later */
12521                 cm = EndOfFile;
12522                 break;
12523             }
12524
12525             if (cm == MoveNumberOne || cm == PositionDiagram ||
12526                 cm == PGNTag || cm == Comment)
12527               break;
12528         }
12529     } else if (cm == GNUChessGame) {
12530         if (gameInfo.event != NULL) {
12531             free(gameInfo.event);
12532         }
12533         gameInfo.event = StrSave(yy_text);
12534     }
12535
12536     startedFromSetupPosition = FALSE;
12537     while (cm == PGNTag) {
12538         if (appData.debugMode)
12539           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12540         err = ParsePGNTag(yy_text, &gameInfo);
12541         if (!err) numPGNTags++;
12542
12543         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12544         if(gameInfo.variant != oldVariant) {
12545             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12546             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12547             InitPosition(TRUE);
12548             oldVariant = gameInfo.variant;
12549             if (appData.debugMode)
12550               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12551         }
12552
12553
12554         if (gameInfo.fen != NULL) {
12555           Board initial_position;
12556           startedFromSetupPosition = TRUE;
12557           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12558             Reset(TRUE, TRUE);
12559             DisplayError(_("Bad FEN position in file"), 0);
12560             return FALSE;
12561           }
12562           CopyBoard(boards[0], initial_position);
12563           if (blackPlaysFirst) {
12564             currentMove = forwardMostMove = backwardMostMove = 1;
12565             CopyBoard(boards[1], initial_position);
12566             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12567             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12568             timeRemaining[0][1] = whiteTimeRemaining;
12569             timeRemaining[1][1] = blackTimeRemaining;
12570             if (commentList[0] != NULL) {
12571               commentList[1] = commentList[0];
12572               commentList[0] = NULL;
12573             }
12574           } else {
12575             currentMove = forwardMostMove = backwardMostMove = 0;
12576           }
12577           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12578           {   int i;
12579               initialRulePlies = FENrulePlies;
12580               for( i=0; i< nrCastlingRights; i++ )
12581                   initialRights[i] = initial_position[CASTLING][i];
12582           }
12583           yyboardindex = forwardMostMove;
12584           free(gameInfo.fen);
12585           gameInfo.fen = NULL;
12586         }
12587
12588         yyboardindex = forwardMostMove;
12589         cm = (ChessMove) Myylex();
12590
12591         /* Handle comments interspersed among the tags */
12592         while (cm == Comment) {
12593             char *p;
12594             if (appData.debugMode)
12595               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12596             p = yy_text;
12597             AppendComment(currentMove, p, FALSE);
12598             yyboardindex = forwardMostMove;
12599             cm = (ChessMove) Myylex();
12600         }
12601     }
12602
12603     /* don't rely on existence of Event tag since if game was
12604      * pasted from clipboard the Event tag may not exist
12605      */
12606     if (numPGNTags > 0){
12607         char *tags;
12608         if (gameInfo.variant == VariantNormal) {
12609           VariantClass v = StringToVariant(gameInfo.event);
12610           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12611           if(v < VariantShogi) gameInfo.variant = v;
12612         }
12613         if (!matchMode) {
12614           if( appData.autoDisplayTags ) {
12615             tags = PGNTags(&gameInfo);
12616             TagsPopUp(tags, CmailMsg());
12617             free(tags);
12618           }
12619         }
12620     } else {
12621         /* Make something up, but don't display it now */
12622         SetGameInfo();
12623         TagsPopDown();
12624     }
12625
12626     if (cm == PositionDiagram) {
12627         int i, j;
12628         char *p;
12629         Board initial_position;
12630
12631         if (appData.debugMode)
12632           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12633
12634         if (!startedFromSetupPosition) {
12635             p = yy_text;
12636             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12637               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12638                 switch (*p) {
12639                   case '{':
12640                   case '[':
12641                   case '-':
12642                   case ' ':
12643                   case '\t':
12644                   case '\n':
12645                   case '\r':
12646                     break;
12647                   default:
12648                     initial_position[i][j++] = CharToPiece(*p);
12649                     break;
12650                 }
12651             while (*p == ' ' || *p == '\t' ||
12652                    *p == '\n' || *p == '\r') p++;
12653
12654             if (strncmp(p, "black", strlen("black"))==0)
12655               blackPlaysFirst = TRUE;
12656             else
12657               blackPlaysFirst = FALSE;
12658             startedFromSetupPosition = TRUE;
12659
12660             CopyBoard(boards[0], initial_position);
12661             if (blackPlaysFirst) {
12662                 currentMove = forwardMostMove = backwardMostMove = 1;
12663                 CopyBoard(boards[1], initial_position);
12664                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12665                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12666                 timeRemaining[0][1] = whiteTimeRemaining;
12667                 timeRemaining[1][1] = blackTimeRemaining;
12668                 if (commentList[0] != NULL) {
12669                     commentList[1] = commentList[0];
12670                     commentList[0] = NULL;
12671                 }
12672             } else {
12673                 currentMove = forwardMostMove = backwardMostMove = 0;
12674             }
12675         }
12676         yyboardindex = forwardMostMove;
12677         cm = (ChessMove) Myylex();
12678     }
12679
12680   if(!creatingBook) {
12681     if (first.pr == NoProc) {
12682         StartChessProgram(&first);
12683     }
12684     InitChessProgram(&first, FALSE);
12685     SendToProgram("force\n", &first);
12686     if (startedFromSetupPosition) {
12687         SendBoard(&first, forwardMostMove);
12688     if (appData.debugMode) {
12689         fprintf(debugFP, "Load Game\n");
12690     }
12691         DisplayBothClocks();
12692     }
12693   }
12694
12695     /* [HGM] server: flag to write setup moves in broadcast file as one */
12696     loadFlag = appData.suppressLoadMoves;
12697
12698     while (cm == Comment) {
12699         char *p;
12700         if (appData.debugMode)
12701           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12702         p = yy_text;
12703         AppendComment(currentMove, p, FALSE);
12704         yyboardindex = forwardMostMove;
12705         cm = (ChessMove) Myylex();
12706     }
12707
12708     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12709         cm == WhiteWins || cm == BlackWins ||
12710         cm == GameIsDrawn || cm == GameUnfinished) {
12711         DisplayMessage("", _("No moves in game"));
12712         if (cmailMsgLoaded) {
12713             if (appData.debugMode)
12714               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12715             ClearHighlights();
12716             flipView = FALSE;
12717         }
12718         DrawPosition(FALSE, boards[currentMove]);
12719         DisplayBothClocks();
12720         gameMode = EditGame;
12721         ModeHighlight();
12722         gameFileFP = NULL;
12723         cmailOldMove = 0;
12724         return TRUE;
12725     }
12726
12727     // [HGM] PV info: routine tests if comment empty
12728     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12729         DisplayComment(currentMove - 1, commentList[currentMove]);
12730     }
12731     if (!matchMode && appData.timeDelay != 0)
12732       DrawPosition(FALSE, boards[currentMove]);
12733
12734     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12735       programStats.ok_to_send = 1;
12736     }
12737
12738     /* if the first token after the PGN tags is a move
12739      * and not move number 1, retrieve it from the parser
12740      */
12741     if (cm != MoveNumberOne)
12742         LoadGameOneMove(cm);
12743
12744     /* load the remaining moves from the file */
12745     while (LoadGameOneMove(EndOfFile)) {
12746       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12747       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12748     }
12749
12750     /* rewind to the start of the game */
12751     currentMove = backwardMostMove;
12752
12753     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12754
12755     if (oldGameMode == AnalyzeFile) {
12756       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12757       AnalyzeFileEvent();
12758     } else
12759     if (oldGameMode == AnalyzeMode) {
12760       AnalyzeFileEvent();
12761     }
12762
12763     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12764         long int w, b; // [HGM] adjourn: restore saved clock times
12765         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12766         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12767             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12768             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12769         }
12770     }
12771
12772     if(creatingBook) return TRUE;
12773     if (!matchMode && pos > 0) {
12774         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12775     } else
12776     if (matchMode || appData.timeDelay == 0) {
12777       ToEndEvent();
12778     } else if (appData.timeDelay > 0) {
12779       AutoPlayGameLoop();
12780     }
12781
12782     if (appData.debugMode)
12783         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12784
12785     loadFlag = 0; /* [HGM] true game starts */
12786     return TRUE;
12787 }
12788
12789 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12790 int
12791 ReloadPosition (int offset)
12792 {
12793     int positionNumber = lastLoadPositionNumber + offset;
12794     if (lastLoadPositionFP == NULL) {
12795         DisplayError(_("No position has been loaded yet"), 0);
12796         return FALSE;
12797     }
12798     if (positionNumber <= 0) {
12799         DisplayError(_("Can't back up any further"), 0);
12800         return FALSE;
12801     }
12802     return LoadPosition(lastLoadPositionFP, positionNumber,
12803                         lastLoadPositionTitle);
12804 }
12805
12806 /* Load the nth position from the given file */
12807 int
12808 LoadPositionFromFile (char *filename, int n, char *title)
12809 {
12810     FILE *f;
12811     char buf[MSG_SIZ];
12812
12813     if (strcmp(filename, "-") == 0) {
12814         return LoadPosition(stdin, n, "stdin");
12815     } else {
12816         f = fopen(filename, "rb");
12817         if (f == NULL) {
12818             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12819             DisplayError(buf, errno);
12820             return FALSE;
12821         } else {
12822             return LoadPosition(f, n, title);
12823         }
12824     }
12825 }
12826
12827 /* Load the nth position from the given open file, and close it */
12828 int
12829 LoadPosition (FILE *f, int positionNumber, char *title)
12830 {
12831     char *p, line[MSG_SIZ];
12832     Board initial_position;
12833     int i, j, fenMode, pn;
12834
12835     if (gameMode == Training )
12836         SetTrainingModeOff();
12837
12838     if (gameMode != BeginningOfGame) {
12839         Reset(FALSE, TRUE);
12840     }
12841     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12842         fclose(lastLoadPositionFP);
12843     }
12844     if (positionNumber == 0) positionNumber = 1;
12845     lastLoadPositionFP = f;
12846     lastLoadPositionNumber = positionNumber;
12847     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12848     if (first.pr == NoProc && !appData.noChessProgram) {
12849       StartChessProgram(&first);
12850       InitChessProgram(&first, FALSE);
12851     }
12852     pn = positionNumber;
12853     if (positionNumber < 0) {
12854         /* Negative position number means to seek to that byte offset */
12855         if (fseek(f, -positionNumber, 0) == -1) {
12856             DisplayError(_("Can't seek on position file"), 0);
12857             return FALSE;
12858         };
12859         pn = 1;
12860     } else {
12861         if (fseek(f, 0, 0) == -1) {
12862             if (f == lastLoadPositionFP ?
12863                 positionNumber == lastLoadPositionNumber + 1 :
12864                 positionNumber == 1) {
12865                 pn = 1;
12866             } else {
12867                 DisplayError(_("Can't seek on position file"), 0);
12868                 return FALSE;
12869             }
12870         }
12871     }
12872     /* See if this file is FEN or old-style xboard */
12873     if (fgets(line, MSG_SIZ, f) == NULL) {
12874         DisplayError(_("Position not found in file"), 0);
12875         return FALSE;
12876     }
12877     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12878     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12879
12880     if (pn >= 2) {
12881         if (fenMode || line[0] == '#') pn--;
12882         while (pn > 0) {
12883             /* skip positions before number pn */
12884             if (fgets(line, MSG_SIZ, f) == NULL) {
12885                 Reset(TRUE, TRUE);
12886                 DisplayError(_("Position not found in file"), 0);
12887                 return FALSE;
12888             }
12889             if (fenMode || line[0] == '#') pn--;
12890         }
12891     }
12892
12893     if (fenMode) {
12894         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12895             DisplayError(_("Bad FEN position in file"), 0);
12896             return FALSE;
12897         }
12898     } else {
12899         (void) fgets(line, MSG_SIZ, f);
12900         (void) fgets(line, MSG_SIZ, f);
12901
12902         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12903             (void) fgets(line, MSG_SIZ, f);
12904             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12905                 if (*p == ' ')
12906                   continue;
12907                 initial_position[i][j++] = CharToPiece(*p);
12908             }
12909         }
12910
12911         blackPlaysFirst = FALSE;
12912         if (!feof(f)) {
12913             (void) fgets(line, MSG_SIZ, f);
12914             if (strncmp(line, "black", strlen("black"))==0)
12915               blackPlaysFirst = TRUE;
12916         }
12917     }
12918     startedFromSetupPosition = TRUE;
12919
12920     CopyBoard(boards[0], initial_position);
12921     if (blackPlaysFirst) {
12922         currentMove = forwardMostMove = backwardMostMove = 1;
12923         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12924         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12925         CopyBoard(boards[1], initial_position);
12926         DisplayMessage("", _("Black to play"));
12927     } else {
12928         currentMove = forwardMostMove = backwardMostMove = 0;
12929         DisplayMessage("", _("White to play"));
12930     }
12931     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12932     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12933         SendToProgram("force\n", &first);
12934         SendBoard(&first, forwardMostMove);
12935     }
12936     if (appData.debugMode) {
12937 int i, j;
12938   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12939   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12940         fprintf(debugFP, "Load Position\n");
12941     }
12942
12943     if (positionNumber > 1) {
12944       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12945         DisplayTitle(line);
12946     } else {
12947         DisplayTitle(title);
12948     }
12949     gameMode = EditGame;
12950     ModeHighlight();
12951     ResetClocks();
12952     timeRemaining[0][1] = whiteTimeRemaining;
12953     timeRemaining[1][1] = blackTimeRemaining;
12954     DrawPosition(FALSE, boards[currentMove]);
12955
12956     return TRUE;
12957 }
12958
12959
12960 void
12961 CopyPlayerNameIntoFileName (char **dest, char *src)
12962 {
12963     while (*src != NULLCHAR && *src != ',') {
12964         if (*src == ' ') {
12965             *(*dest)++ = '_';
12966             src++;
12967         } else {
12968             *(*dest)++ = *src++;
12969         }
12970     }
12971 }
12972
12973 char *
12974 DefaultFileName (char *ext)
12975 {
12976     static char def[MSG_SIZ];
12977     char *p;
12978
12979     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12980         p = def;
12981         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12982         *p++ = '-';
12983         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12984         *p++ = '.';
12985         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12986     } else {
12987         def[0] = NULLCHAR;
12988     }
12989     return def;
12990 }
12991
12992 /* Save the current game to the given file */
12993 int
12994 SaveGameToFile (char *filename, int append)
12995 {
12996     FILE *f;
12997     char buf[MSG_SIZ];
12998     int result, i, t,tot=0;
12999
13000     if (strcmp(filename, "-") == 0) {
13001         return SaveGame(stdout, 0, NULL);
13002     } else {
13003         for(i=0; i<10; i++) { // upto 10 tries
13004              f = fopen(filename, append ? "a" : "w");
13005              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13006              if(f || errno != 13) break;
13007              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13008              tot += t;
13009         }
13010         if (f == NULL) {
13011             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13012             DisplayError(buf, errno);
13013             return FALSE;
13014         } else {
13015             safeStrCpy(buf, lastMsg, MSG_SIZ);
13016             DisplayMessage(_("Waiting for access to save file"), "");
13017             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13018             DisplayMessage(_("Saving game"), "");
13019             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13020             result = SaveGame(f, 0, NULL);
13021             DisplayMessage(buf, "");
13022             return result;
13023         }
13024     }
13025 }
13026
13027 char *
13028 SavePart (char *str)
13029 {
13030     static char buf[MSG_SIZ];
13031     char *p;
13032
13033     p = strchr(str, ' ');
13034     if (p == NULL) return str;
13035     strncpy(buf, str, p - str);
13036     buf[p - str] = NULLCHAR;
13037     return buf;
13038 }
13039
13040 #define PGN_MAX_LINE 75
13041
13042 #define PGN_SIDE_WHITE  0
13043 #define PGN_SIDE_BLACK  1
13044
13045 static int
13046 FindFirstMoveOutOfBook (int side)
13047 {
13048     int result = -1;
13049
13050     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13051         int index = backwardMostMove;
13052         int has_book_hit = 0;
13053
13054         if( (index % 2) != side ) {
13055             index++;
13056         }
13057
13058         while( index < forwardMostMove ) {
13059             /* Check to see if engine is in book */
13060             int depth = pvInfoList[index].depth;
13061             int score = pvInfoList[index].score;
13062             int in_book = 0;
13063
13064             if( depth <= 2 ) {
13065                 in_book = 1;
13066             }
13067             else if( score == 0 && depth == 63 ) {
13068                 in_book = 1; /* Zappa */
13069             }
13070             else if( score == 2 && depth == 99 ) {
13071                 in_book = 1; /* Abrok */
13072             }
13073
13074             has_book_hit += in_book;
13075
13076             if( ! in_book ) {
13077                 result = index;
13078
13079                 break;
13080             }
13081
13082             index += 2;
13083         }
13084     }
13085
13086     return result;
13087 }
13088
13089 void
13090 GetOutOfBookInfo (char * buf)
13091 {
13092     int oob[2];
13093     int i;
13094     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13095
13096     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13097     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13098
13099     *buf = '\0';
13100
13101     if( oob[0] >= 0 || oob[1] >= 0 ) {
13102         for( i=0; i<2; i++ ) {
13103             int idx = oob[i];
13104
13105             if( idx >= 0 ) {
13106                 if( i > 0 && oob[0] >= 0 ) {
13107                     strcat( buf, "   " );
13108                 }
13109
13110                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13111                 sprintf( buf+strlen(buf), "%s%.2f",
13112                     pvInfoList[idx].score >= 0 ? "+" : "",
13113                     pvInfoList[idx].score / 100.0 );
13114             }
13115         }
13116     }
13117 }
13118
13119 /* Save game in PGN style and close the file */
13120 int
13121 SaveGamePGN (FILE *f)
13122 {
13123     int i, offset, linelen, newblock;
13124 //    char *movetext;
13125     char numtext[32];
13126     int movelen, numlen, blank;
13127     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13128
13129     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13130
13131     PrintPGNTags(f, &gameInfo);
13132
13133     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13134
13135     if (backwardMostMove > 0 || startedFromSetupPosition) {
13136         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13137         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13138         fprintf(f, "\n{--------------\n");
13139         PrintPosition(f, backwardMostMove);
13140         fprintf(f, "--------------}\n");
13141         free(fen);
13142     }
13143     else {
13144         /* [AS] Out of book annotation */
13145         if( appData.saveOutOfBookInfo ) {
13146             char buf[64];
13147
13148             GetOutOfBookInfo( buf );
13149
13150             if( buf[0] != '\0' ) {
13151                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13152             }
13153         }
13154
13155         fprintf(f, "\n");
13156     }
13157
13158     i = backwardMostMove;
13159     linelen = 0;
13160     newblock = TRUE;
13161
13162     while (i < forwardMostMove) {
13163         /* Print comments preceding this move */
13164         if (commentList[i] != NULL) {
13165             if (linelen > 0) fprintf(f, "\n");
13166             fprintf(f, "%s", commentList[i]);
13167             linelen = 0;
13168             newblock = TRUE;
13169         }
13170
13171         /* Format move number */
13172         if ((i % 2) == 0)
13173           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13174         else
13175           if (newblock)
13176             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13177           else
13178             numtext[0] = NULLCHAR;
13179
13180         numlen = strlen(numtext);
13181         newblock = FALSE;
13182
13183         /* Print move number */
13184         blank = linelen > 0 && numlen > 0;
13185         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13186             fprintf(f, "\n");
13187             linelen = 0;
13188             blank = 0;
13189         }
13190         if (blank) {
13191             fprintf(f, " ");
13192             linelen++;
13193         }
13194         fprintf(f, "%s", numtext);
13195         linelen += numlen;
13196
13197         /* Get move */
13198         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13199         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13200
13201         /* Print move */
13202         blank = linelen > 0 && movelen > 0;
13203         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13204             fprintf(f, "\n");
13205             linelen = 0;
13206             blank = 0;
13207         }
13208         if (blank) {
13209             fprintf(f, " ");
13210             linelen++;
13211         }
13212         fprintf(f, "%s", move_buffer);
13213         linelen += movelen;
13214
13215         /* [AS] Add PV info if present */
13216         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13217             /* [HGM] add time */
13218             char buf[MSG_SIZ]; int seconds;
13219
13220             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13221
13222             if( seconds <= 0)
13223               buf[0] = 0;
13224             else
13225               if( seconds < 30 )
13226                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13227               else
13228                 {
13229                   seconds = (seconds + 4)/10; // round to full seconds
13230                   if( seconds < 60 )
13231                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13232                   else
13233                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13234                 }
13235
13236             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13237                       pvInfoList[i].score >= 0 ? "+" : "",
13238                       pvInfoList[i].score / 100.0,
13239                       pvInfoList[i].depth,
13240                       buf );
13241
13242             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13243
13244             /* Print score/depth */
13245             blank = linelen > 0 && movelen > 0;
13246             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13247                 fprintf(f, "\n");
13248                 linelen = 0;
13249                 blank = 0;
13250             }
13251             if (blank) {
13252                 fprintf(f, " ");
13253                 linelen++;
13254             }
13255             fprintf(f, "%s", move_buffer);
13256             linelen += movelen;
13257         }
13258
13259         i++;
13260     }
13261
13262     /* Start a new line */
13263     if (linelen > 0) fprintf(f, "\n");
13264
13265     /* Print comments after last move */
13266     if (commentList[i] != NULL) {
13267         fprintf(f, "%s\n", commentList[i]);
13268     }
13269
13270     /* Print result */
13271     if (gameInfo.resultDetails != NULL &&
13272         gameInfo.resultDetails[0] != NULLCHAR) {
13273         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13274         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13275            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13276             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13277         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13278     } else {
13279         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13280     }
13281
13282     fclose(f);
13283     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13284     return TRUE;
13285 }
13286
13287 /* Save game in old style and close the file */
13288 int
13289 SaveGameOldStyle (FILE *f)
13290 {
13291     int i, offset;
13292     time_t tm;
13293
13294     tm = time((time_t *) NULL);
13295
13296     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13297     PrintOpponents(f);
13298
13299     if (backwardMostMove > 0 || startedFromSetupPosition) {
13300         fprintf(f, "\n[--------------\n");
13301         PrintPosition(f, backwardMostMove);
13302         fprintf(f, "--------------]\n");
13303     } else {
13304         fprintf(f, "\n");
13305     }
13306
13307     i = backwardMostMove;
13308     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13309
13310     while (i < forwardMostMove) {
13311         if (commentList[i] != NULL) {
13312             fprintf(f, "[%s]\n", commentList[i]);
13313         }
13314
13315         if ((i % 2) == 1) {
13316             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13317             i++;
13318         } else {
13319             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13320             i++;
13321             if (commentList[i] != NULL) {
13322                 fprintf(f, "\n");
13323                 continue;
13324             }
13325             if (i >= forwardMostMove) {
13326                 fprintf(f, "\n");
13327                 break;
13328             }
13329             fprintf(f, "%s\n", parseList[i]);
13330             i++;
13331         }
13332     }
13333
13334     if (commentList[i] != NULL) {
13335         fprintf(f, "[%s]\n", commentList[i]);
13336     }
13337
13338     /* This isn't really the old style, but it's close enough */
13339     if (gameInfo.resultDetails != NULL &&
13340         gameInfo.resultDetails[0] != NULLCHAR) {
13341         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13342                 gameInfo.resultDetails);
13343     } else {
13344         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13345     }
13346
13347     fclose(f);
13348     return TRUE;
13349 }
13350
13351 /* Save the current game to open file f and close the file */
13352 int
13353 SaveGame (FILE *f, int dummy, char *dummy2)
13354 {
13355     if (gameMode == EditPosition) EditPositionDone(TRUE);
13356     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13357     if (appData.oldSaveStyle)
13358       return SaveGameOldStyle(f);
13359     else
13360       return SaveGamePGN(f);
13361 }
13362
13363 /* Save the current position to the given file */
13364 int
13365 SavePositionToFile (char *filename)
13366 {
13367     FILE *f;
13368     char buf[MSG_SIZ];
13369
13370     if (strcmp(filename, "-") == 0) {
13371         return SavePosition(stdout, 0, NULL);
13372     } else {
13373         f = fopen(filename, "a");
13374         if (f == NULL) {
13375             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13376             DisplayError(buf, errno);
13377             return FALSE;
13378         } else {
13379             safeStrCpy(buf, lastMsg, MSG_SIZ);
13380             DisplayMessage(_("Waiting for access to save file"), "");
13381             flock(fileno(f), LOCK_EX); // [HGM] lock
13382             DisplayMessage(_("Saving position"), "");
13383             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13384             SavePosition(f, 0, NULL);
13385             DisplayMessage(buf, "");
13386             return TRUE;
13387         }
13388     }
13389 }
13390
13391 /* Save the current position to the given open file and close the file */
13392 int
13393 SavePosition (FILE *f, int dummy, char *dummy2)
13394 {
13395     time_t tm;
13396     char *fen;
13397
13398     if (gameMode == EditPosition) EditPositionDone(TRUE);
13399     if (appData.oldSaveStyle) {
13400         tm = time((time_t *) NULL);
13401
13402         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13403         PrintOpponents(f);
13404         fprintf(f, "[--------------\n");
13405         PrintPosition(f, currentMove);
13406         fprintf(f, "--------------]\n");
13407     } else {
13408         fen = PositionToFEN(currentMove, NULL, 1);
13409         fprintf(f, "%s\n", fen);
13410         free(fen);
13411     }
13412     fclose(f);
13413     return TRUE;
13414 }
13415
13416 void
13417 ReloadCmailMsgEvent (int unregister)
13418 {
13419 #if !WIN32
13420     static char *inFilename = NULL;
13421     static char *outFilename;
13422     int i;
13423     struct stat inbuf, outbuf;
13424     int status;
13425
13426     /* Any registered moves are unregistered if unregister is set, */
13427     /* i.e. invoked by the signal handler */
13428     if (unregister) {
13429         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13430             cmailMoveRegistered[i] = FALSE;
13431             if (cmailCommentList[i] != NULL) {
13432                 free(cmailCommentList[i]);
13433                 cmailCommentList[i] = NULL;
13434             }
13435         }
13436         nCmailMovesRegistered = 0;
13437     }
13438
13439     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13440         cmailResult[i] = CMAIL_NOT_RESULT;
13441     }
13442     nCmailResults = 0;
13443
13444     if (inFilename == NULL) {
13445         /* Because the filenames are static they only get malloced once  */
13446         /* and they never get freed                                      */
13447         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13448         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13449
13450         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13451         sprintf(outFilename, "%s.out", appData.cmailGameName);
13452     }
13453
13454     status = stat(outFilename, &outbuf);
13455     if (status < 0) {
13456         cmailMailedMove = FALSE;
13457     } else {
13458         status = stat(inFilename, &inbuf);
13459         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13460     }
13461
13462     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13463        counts the games, notes how each one terminated, etc.
13464
13465        It would be nice to remove this kludge and instead gather all
13466        the information while building the game list.  (And to keep it
13467        in the game list nodes instead of having a bunch of fixed-size
13468        parallel arrays.)  Note this will require getting each game's
13469        termination from the PGN tags, as the game list builder does
13470        not process the game moves.  --mann
13471        */
13472     cmailMsgLoaded = TRUE;
13473     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13474
13475     /* Load first game in the file or popup game menu */
13476     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13477
13478 #endif /* !WIN32 */
13479     return;
13480 }
13481
13482 int
13483 RegisterMove ()
13484 {
13485     FILE *f;
13486     char string[MSG_SIZ];
13487
13488     if (   cmailMailedMove
13489         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13490         return TRUE;            /* Allow free viewing  */
13491     }
13492
13493     /* Unregister move to ensure that we don't leave RegisterMove        */
13494     /* with the move registered when the conditions for registering no   */
13495     /* longer hold                                                       */
13496     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13497         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13498         nCmailMovesRegistered --;
13499
13500         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13501           {
13502               free(cmailCommentList[lastLoadGameNumber - 1]);
13503               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13504           }
13505     }
13506
13507     if (cmailOldMove == -1) {
13508         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13509         return FALSE;
13510     }
13511
13512     if (currentMove > cmailOldMove + 1) {
13513         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13514         return FALSE;
13515     }
13516
13517     if (currentMove < cmailOldMove) {
13518         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13519         return FALSE;
13520     }
13521
13522     if (forwardMostMove > currentMove) {
13523         /* Silently truncate extra moves */
13524         TruncateGame();
13525     }
13526
13527     if (   (currentMove == cmailOldMove + 1)
13528         || (   (currentMove == cmailOldMove)
13529             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13530                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13531         if (gameInfo.result != GameUnfinished) {
13532             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13533         }
13534
13535         if (commentList[currentMove] != NULL) {
13536             cmailCommentList[lastLoadGameNumber - 1]
13537               = StrSave(commentList[currentMove]);
13538         }
13539         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13540
13541         if (appData.debugMode)
13542           fprintf(debugFP, "Saving %s for game %d\n",
13543                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13544
13545         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13546
13547         f = fopen(string, "w");
13548         if (appData.oldSaveStyle) {
13549             SaveGameOldStyle(f); /* also closes the file */
13550
13551             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13552             f = fopen(string, "w");
13553             SavePosition(f, 0, NULL); /* also closes the file */
13554         } else {
13555             fprintf(f, "{--------------\n");
13556             PrintPosition(f, currentMove);
13557             fprintf(f, "--------------}\n\n");
13558
13559             SaveGame(f, 0, NULL); /* also closes the file*/
13560         }
13561
13562         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13563         nCmailMovesRegistered ++;
13564     } else if (nCmailGames == 1) {
13565         DisplayError(_("You have not made a move yet"), 0);
13566         return FALSE;
13567     }
13568
13569     return TRUE;
13570 }
13571
13572 void
13573 MailMoveEvent ()
13574 {
13575 #if !WIN32
13576     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13577     FILE *commandOutput;
13578     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13579     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13580     int nBuffers;
13581     int i;
13582     int archived;
13583     char *arcDir;
13584
13585     if (! cmailMsgLoaded) {
13586         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13587         return;
13588     }
13589
13590     if (nCmailGames == nCmailResults) {
13591         DisplayError(_("No unfinished games"), 0);
13592         return;
13593     }
13594
13595 #if CMAIL_PROHIBIT_REMAIL
13596     if (cmailMailedMove) {
13597       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);
13598         DisplayError(msg, 0);
13599         return;
13600     }
13601 #endif
13602
13603     if (! (cmailMailedMove || RegisterMove())) return;
13604
13605     if (   cmailMailedMove
13606         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13607       snprintf(string, MSG_SIZ, partCommandString,
13608                appData.debugMode ? " -v" : "", appData.cmailGameName);
13609         commandOutput = popen(string, "r");
13610
13611         if (commandOutput == NULL) {
13612             DisplayError(_("Failed to invoke cmail"), 0);
13613         } else {
13614             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13615                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13616             }
13617             if (nBuffers > 1) {
13618                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13619                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13620                 nBytes = MSG_SIZ - 1;
13621             } else {
13622                 (void) memcpy(msg, buffer, nBytes);
13623             }
13624             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13625
13626             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13627                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13628
13629                 archived = TRUE;
13630                 for (i = 0; i < nCmailGames; i ++) {
13631                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13632                         archived = FALSE;
13633                     }
13634                 }
13635                 if (   archived
13636                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13637                         != NULL)) {
13638                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13639                            arcDir,
13640                            appData.cmailGameName,
13641                            gameInfo.date);
13642                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13643                     cmailMsgLoaded = FALSE;
13644                 }
13645             }
13646
13647             DisplayInformation(msg);
13648             pclose(commandOutput);
13649         }
13650     } else {
13651         if ((*cmailMsg) != '\0') {
13652             DisplayInformation(cmailMsg);
13653         }
13654     }
13655
13656     return;
13657 #endif /* !WIN32 */
13658 }
13659
13660 char *
13661 CmailMsg ()
13662 {
13663 #if WIN32
13664     return NULL;
13665 #else
13666     int  prependComma = 0;
13667     char number[5];
13668     char string[MSG_SIZ];       /* Space for game-list */
13669     int  i;
13670
13671     if (!cmailMsgLoaded) return "";
13672
13673     if (cmailMailedMove) {
13674       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13675     } else {
13676         /* Create a list of games left */
13677       snprintf(string, MSG_SIZ, "[");
13678         for (i = 0; i < nCmailGames; i ++) {
13679             if (! (   cmailMoveRegistered[i]
13680                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13681                 if (prependComma) {
13682                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13683                 } else {
13684                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13685                     prependComma = 1;
13686                 }
13687
13688                 strcat(string, number);
13689             }
13690         }
13691         strcat(string, "]");
13692
13693         if (nCmailMovesRegistered + nCmailResults == 0) {
13694             switch (nCmailGames) {
13695               case 1:
13696                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13697                 break;
13698
13699               case 2:
13700                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13701                 break;
13702
13703               default:
13704                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13705                          nCmailGames);
13706                 break;
13707             }
13708         } else {
13709             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13710               case 1:
13711                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13712                          string);
13713                 break;
13714
13715               case 0:
13716                 if (nCmailResults == nCmailGames) {
13717                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13718                 } else {
13719                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13720                 }
13721                 break;
13722
13723               default:
13724                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13725                          string);
13726             }
13727         }
13728     }
13729     return cmailMsg;
13730 #endif /* WIN32 */
13731 }
13732
13733 void
13734 ResetGameEvent ()
13735 {
13736     if (gameMode == Training)
13737       SetTrainingModeOff();
13738
13739     Reset(TRUE, TRUE);
13740     cmailMsgLoaded = FALSE;
13741     if (appData.icsActive) {
13742       SendToICS(ics_prefix);
13743       SendToICS("refresh\n");
13744     }
13745 }
13746
13747 void
13748 ExitEvent (int status)
13749 {
13750     exiting++;
13751     if (exiting > 2) {
13752       /* Give up on clean exit */
13753       exit(status);
13754     }
13755     if (exiting > 1) {
13756       /* Keep trying for clean exit */
13757       return;
13758     }
13759
13760     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13761
13762     if (telnetISR != NULL) {
13763       RemoveInputSource(telnetISR);
13764     }
13765     if (icsPR != NoProc) {
13766       DestroyChildProcess(icsPR, TRUE);
13767     }
13768
13769     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13770     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13771
13772     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13773     /* make sure this other one finishes before killing it!                  */
13774     if(endingGame) { int count = 0;
13775         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13776         while(endingGame && count++ < 10) DoSleep(1);
13777         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13778     }
13779
13780     /* Kill off chess programs */
13781     if (first.pr != NoProc) {
13782         ExitAnalyzeMode();
13783
13784         DoSleep( appData.delayBeforeQuit );
13785         SendToProgram("quit\n", &first);
13786         DoSleep( appData.delayAfterQuit );
13787         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13788     }
13789     if (second.pr != NoProc) {
13790         DoSleep( appData.delayBeforeQuit );
13791         SendToProgram("quit\n", &second);
13792         DoSleep( appData.delayAfterQuit );
13793         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13794     }
13795     if (first.isr != NULL) {
13796         RemoveInputSource(first.isr);
13797     }
13798     if (second.isr != NULL) {
13799         RemoveInputSource(second.isr);
13800     }
13801
13802     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13803     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13804
13805     ShutDownFrontEnd();
13806     exit(status);
13807 }
13808
13809 void
13810 PauseEngine (ChessProgramState *cps)
13811 {
13812     SendToProgram("pause\n", cps);
13813     cps->pause = 2;
13814 }
13815
13816 void
13817 UnPauseEngine (ChessProgramState *cps)
13818 {
13819     SendToProgram("resume\n", cps);
13820     cps->pause = 1;
13821 }
13822
13823 void
13824 PauseEvent ()
13825 {
13826     if (appData.debugMode)
13827         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13828     if (pausing) {
13829         pausing = FALSE;
13830         ModeHighlight();
13831         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13832             StartClocks();
13833             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13834                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13835                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13836             }
13837             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13838             HandleMachineMove(stashedInputMove, stalledEngine);
13839             stalledEngine = NULL;
13840             return;
13841         }
13842         if (gameMode == MachinePlaysWhite ||
13843             gameMode == TwoMachinesPlay   ||
13844             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13845             if(first.pause)  UnPauseEngine(&first);
13846             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13847             if(second.pause) UnPauseEngine(&second);
13848             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13849             StartClocks();
13850         } else {
13851             DisplayBothClocks();
13852         }
13853         if (gameMode == PlayFromGameFile) {
13854             if (appData.timeDelay >= 0)
13855                 AutoPlayGameLoop();
13856         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13857             Reset(FALSE, TRUE);
13858             SendToICS(ics_prefix);
13859             SendToICS("refresh\n");
13860         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13861             ForwardInner(forwardMostMove);
13862         }
13863         pauseExamInvalid = FALSE;
13864     } else {
13865         switch (gameMode) {
13866           default:
13867             return;
13868           case IcsExamining:
13869             pauseExamForwardMostMove = forwardMostMove;
13870             pauseExamInvalid = FALSE;
13871             /* fall through */
13872           case IcsObserving:
13873           case IcsPlayingWhite:
13874           case IcsPlayingBlack:
13875             pausing = TRUE;
13876             ModeHighlight();
13877             return;
13878           case PlayFromGameFile:
13879             (void) StopLoadGameTimer();
13880             pausing = TRUE;
13881             ModeHighlight();
13882             break;
13883           case BeginningOfGame:
13884             if (appData.icsActive) return;
13885             /* else fall through */
13886           case MachinePlaysWhite:
13887           case MachinePlaysBlack:
13888           case TwoMachinesPlay:
13889             if (forwardMostMove == 0)
13890               return;           /* don't pause if no one has moved */
13891             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13892                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13893                 if(onMove->pause) {           // thinking engine can be paused
13894                     PauseEngine(onMove);      // do it
13895                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13896                         PauseEngine(onMove->other);
13897                     else
13898                         SendToProgram("easy\n", onMove->other);
13899                     StopClocks();
13900                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13901             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13902                 if(first.pause) {
13903                     PauseEngine(&first);
13904                     StopClocks();
13905                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13906             } else { // human on move, pause pondering by either method
13907                 if(first.pause)
13908                     PauseEngine(&first);
13909                 else if(appData.ponderNextMove)
13910                     SendToProgram("easy\n", &first);
13911                 StopClocks();
13912             }
13913             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13914           case AnalyzeMode:
13915             pausing = TRUE;
13916             ModeHighlight();
13917             break;
13918         }
13919     }
13920 }
13921
13922 void
13923 EditCommentEvent ()
13924 {
13925     char title[MSG_SIZ];
13926
13927     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13928       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13929     } else {
13930       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13931                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13932                parseList[currentMove - 1]);
13933     }
13934
13935     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13936 }
13937
13938
13939 void
13940 EditTagsEvent ()
13941 {
13942     char *tags = PGNTags(&gameInfo);
13943     bookUp = FALSE;
13944     EditTagsPopUp(tags, NULL);
13945     free(tags);
13946 }
13947
13948 void
13949 ToggleSecond ()
13950 {
13951   if(second.analyzing) {
13952     SendToProgram("exit\n", &second);
13953     second.analyzing = FALSE;
13954   } else {
13955     if (second.pr == NoProc) StartChessProgram(&second);
13956     InitChessProgram(&second, FALSE);
13957     FeedMovesToProgram(&second, currentMove);
13958
13959     SendToProgram("analyze\n", &second);
13960     second.analyzing = TRUE;
13961   }
13962 }
13963
13964 /* Toggle ShowThinking */
13965 void
13966 ToggleShowThinking()
13967 {
13968   appData.showThinking = !appData.showThinking;
13969   ShowThinkingEvent();
13970 }
13971
13972 int
13973 AnalyzeModeEvent ()
13974 {
13975     char buf[MSG_SIZ];
13976
13977     if (!first.analysisSupport) {
13978       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13979       DisplayError(buf, 0);
13980       return 0;
13981     }
13982     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13983     if (appData.icsActive) {
13984         if (gameMode != IcsObserving) {
13985           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13986             DisplayError(buf, 0);
13987             /* secure check */
13988             if (appData.icsEngineAnalyze) {
13989                 if (appData.debugMode)
13990                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13991                 ExitAnalyzeMode();
13992                 ModeHighlight();
13993             }
13994             return 0;
13995         }
13996         /* if enable, user wants to disable icsEngineAnalyze */
13997         if (appData.icsEngineAnalyze) {
13998                 ExitAnalyzeMode();
13999                 ModeHighlight();
14000                 return 0;
14001         }
14002         appData.icsEngineAnalyze = TRUE;
14003         if (appData.debugMode)
14004             fprintf(debugFP, "ICS engine analyze starting... \n");
14005     }
14006
14007     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14008     if (appData.noChessProgram || gameMode == AnalyzeMode)
14009       return 0;
14010
14011     if (gameMode != AnalyzeFile) {
14012         if (!appData.icsEngineAnalyze) {
14013                EditGameEvent();
14014                if (gameMode != EditGame) return 0;
14015         }
14016         if (!appData.showThinking) ToggleShowThinking();
14017         ResurrectChessProgram();
14018         SendToProgram("analyze\n", &first);
14019         first.analyzing = TRUE;
14020         /*first.maybeThinking = TRUE;*/
14021         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14022         EngineOutputPopUp();
14023     }
14024     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14025     pausing = FALSE;
14026     ModeHighlight();
14027     SetGameInfo();
14028
14029     StartAnalysisClock();
14030     GetTimeMark(&lastNodeCountTime);
14031     lastNodeCount = 0;
14032     return 1;
14033 }
14034
14035 void
14036 AnalyzeFileEvent ()
14037 {
14038     if (appData.noChessProgram || gameMode == AnalyzeFile)
14039       return;
14040
14041     if (!first.analysisSupport) {
14042       char buf[MSG_SIZ];
14043       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14044       DisplayError(buf, 0);
14045       return;
14046     }
14047
14048     if (gameMode != AnalyzeMode) {
14049         keepInfo = 1; // mere annotating should not alter PGN tags
14050         EditGameEvent();
14051         keepInfo = 0;
14052         if (gameMode != EditGame) return;
14053         if (!appData.showThinking) ToggleShowThinking();
14054         ResurrectChessProgram();
14055         SendToProgram("analyze\n", &first);
14056         first.analyzing = TRUE;
14057         /*first.maybeThinking = TRUE;*/
14058         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14059         EngineOutputPopUp();
14060     }
14061     gameMode = AnalyzeFile;
14062     pausing = FALSE;
14063     ModeHighlight();
14064
14065     StartAnalysisClock();
14066     GetTimeMark(&lastNodeCountTime);
14067     lastNodeCount = 0;
14068     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14069     AnalysisPeriodicEvent(1);
14070 }
14071
14072 void
14073 MachineWhiteEvent ()
14074 {
14075     char buf[MSG_SIZ];
14076     char *bookHit = NULL;
14077
14078     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14079       return;
14080
14081
14082     if (gameMode == PlayFromGameFile ||
14083         gameMode == TwoMachinesPlay  ||
14084         gameMode == Training         ||
14085         gameMode == AnalyzeMode      ||
14086         gameMode == EndOfGame)
14087         EditGameEvent();
14088
14089     if (gameMode == EditPosition)
14090         EditPositionDone(TRUE);
14091
14092     if (!WhiteOnMove(currentMove)) {
14093         DisplayError(_("It is not White's turn"), 0);
14094         return;
14095     }
14096
14097     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14098       ExitAnalyzeMode();
14099
14100     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14101         gameMode == AnalyzeFile)
14102         TruncateGame();
14103
14104     ResurrectChessProgram();    /* in case it isn't running */
14105     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14106         gameMode = MachinePlaysWhite;
14107         ResetClocks();
14108     } else
14109     gameMode = MachinePlaysWhite;
14110     pausing = FALSE;
14111     ModeHighlight();
14112     SetGameInfo();
14113     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14114     DisplayTitle(buf);
14115     if (first.sendName) {
14116       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14117       SendToProgram(buf, &first);
14118     }
14119     if (first.sendTime) {
14120       if (first.useColors) {
14121         SendToProgram("black\n", &first); /*gnu kludge*/
14122       }
14123       SendTimeRemaining(&first, TRUE);
14124     }
14125     if (first.useColors) {
14126       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14127     }
14128     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14129     SetMachineThinkingEnables();
14130     first.maybeThinking = TRUE;
14131     StartClocks();
14132     firstMove = FALSE;
14133
14134     if (appData.autoFlipView && !flipView) {
14135       flipView = !flipView;
14136       DrawPosition(FALSE, NULL);
14137       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14138     }
14139
14140     if(bookHit) { // [HGM] book: simulate book reply
14141         static char bookMove[MSG_SIZ]; // a bit generous?
14142
14143         programStats.nodes = programStats.depth = programStats.time =
14144         programStats.score = programStats.got_only_move = 0;
14145         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14146
14147         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14148         strcat(bookMove, bookHit);
14149         HandleMachineMove(bookMove, &first);
14150     }
14151 }
14152
14153 void
14154 MachineBlackEvent ()
14155 {
14156   char buf[MSG_SIZ];
14157   char *bookHit = NULL;
14158
14159     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14160         return;
14161
14162
14163     if (gameMode == PlayFromGameFile ||
14164         gameMode == TwoMachinesPlay  ||
14165         gameMode == Training         ||
14166         gameMode == AnalyzeMode      ||
14167         gameMode == EndOfGame)
14168         EditGameEvent();
14169
14170     if (gameMode == EditPosition)
14171         EditPositionDone(TRUE);
14172
14173     if (WhiteOnMove(currentMove)) {
14174         DisplayError(_("It is not Black's turn"), 0);
14175         return;
14176     }
14177
14178     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14179       ExitAnalyzeMode();
14180
14181     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14182         gameMode == AnalyzeFile)
14183         TruncateGame();
14184
14185     ResurrectChessProgram();    /* in case it isn't running */
14186     gameMode = MachinePlaysBlack;
14187     pausing = FALSE;
14188     ModeHighlight();
14189     SetGameInfo();
14190     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14191     DisplayTitle(buf);
14192     if (first.sendName) {
14193       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14194       SendToProgram(buf, &first);
14195     }
14196     if (first.sendTime) {
14197       if (first.useColors) {
14198         SendToProgram("white\n", &first); /*gnu kludge*/
14199       }
14200       SendTimeRemaining(&first, FALSE);
14201     }
14202     if (first.useColors) {
14203       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14204     }
14205     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14206     SetMachineThinkingEnables();
14207     first.maybeThinking = TRUE;
14208     StartClocks();
14209
14210     if (appData.autoFlipView && flipView) {
14211       flipView = !flipView;
14212       DrawPosition(FALSE, NULL);
14213       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14214     }
14215     if(bookHit) { // [HGM] book: simulate book reply
14216         static char bookMove[MSG_SIZ]; // a bit generous?
14217
14218         programStats.nodes = programStats.depth = programStats.time =
14219         programStats.score = programStats.got_only_move = 0;
14220         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14221
14222         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14223         strcat(bookMove, bookHit);
14224         HandleMachineMove(bookMove, &first);
14225     }
14226 }
14227
14228
14229 void
14230 DisplayTwoMachinesTitle ()
14231 {
14232     char buf[MSG_SIZ];
14233     if (appData.matchGames > 0) {
14234         if(appData.tourneyFile[0]) {
14235           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14236                    gameInfo.white, _("vs."), gameInfo.black,
14237                    nextGame+1, appData.matchGames+1,
14238                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14239         } else
14240         if (first.twoMachinesColor[0] == 'w') {
14241           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14242                    gameInfo.white, _("vs."),  gameInfo.black,
14243                    first.matchWins, second.matchWins,
14244                    matchGame - 1 - (first.matchWins + second.matchWins));
14245         } else {
14246           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14247                    gameInfo.white, _("vs."), gameInfo.black,
14248                    second.matchWins, first.matchWins,
14249                    matchGame - 1 - (first.matchWins + second.matchWins));
14250         }
14251     } else {
14252       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14253     }
14254     DisplayTitle(buf);
14255 }
14256
14257 void
14258 SettingsMenuIfReady ()
14259 {
14260   if (second.lastPing != second.lastPong) {
14261     DisplayMessage("", _("Waiting for second chess program"));
14262     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14263     return;
14264   }
14265   ThawUI();
14266   DisplayMessage("", "");
14267   SettingsPopUp(&second);
14268 }
14269
14270 int
14271 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14272 {
14273     char buf[MSG_SIZ];
14274     if (cps->pr == NoProc) {
14275         StartChessProgram(cps);
14276         if (cps->protocolVersion == 1) {
14277           retry();
14278           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14279         } else {
14280           /* kludge: allow timeout for initial "feature" command */
14281           if(retry != TwoMachinesEventIfReady) FreezeUI();
14282           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14283           DisplayMessage("", buf);
14284           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14285         }
14286         return 1;
14287     }
14288     return 0;
14289 }
14290
14291 void
14292 TwoMachinesEvent P((void))
14293 {
14294     int i;
14295     char buf[MSG_SIZ];
14296     ChessProgramState *onmove;
14297     char *bookHit = NULL;
14298     static int stalling = 0;
14299     TimeMark now;
14300     long wait;
14301
14302     if (appData.noChessProgram) return;
14303
14304     switch (gameMode) {
14305       case TwoMachinesPlay:
14306         return;
14307       case MachinePlaysWhite:
14308       case MachinePlaysBlack:
14309         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14310             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14311             return;
14312         }
14313         /* fall through */
14314       case BeginningOfGame:
14315       case PlayFromGameFile:
14316       case EndOfGame:
14317         EditGameEvent();
14318         if (gameMode != EditGame) return;
14319         break;
14320       case EditPosition:
14321         EditPositionDone(TRUE);
14322         break;
14323       case AnalyzeMode:
14324       case AnalyzeFile:
14325         ExitAnalyzeMode();
14326         break;
14327       case EditGame:
14328       default:
14329         break;
14330     }
14331
14332 //    forwardMostMove = currentMove;
14333     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14334     startingEngine = TRUE;
14335
14336     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14337
14338     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14339     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14340       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14341       return;
14342     }
14343     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14344
14345     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14346         startingEngine = FALSE;
14347         DisplayError("second engine does not play this", 0);
14348         return;
14349     }
14350
14351     if(!stalling) {
14352       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14353       SendToProgram("force\n", &second);
14354       stalling = 1;
14355       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14356       return;
14357     }
14358     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14359     if(appData.matchPause>10000 || appData.matchPause<10)
14360                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14361     wait = SubtractTimeMarks(&now, &pauseStart);
14362     if(wait < appData.matchPause) {
14363         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14364         return;
14365     }
14366     // we are now committed to starting the game
14367     stalling = 0;
14368     DisplayMessage("", "");
14369     if (startedFromSetupPosition) {
14370         SendBoard(&second, backwardMostMove);
14371     if (appData.debugMode) {
14372         fprintf(debugFP, "Two Machines\n");
14373     }
14374     }
14375     for (i = backwardMostMove; i < forwardMostMove; i++) {
14376         SendMoveToProgram(i, &second);
14377     }
14378
14379     gameMode = TwoMachinesPlay;
14380     pausing = startingEngine = FALSE;
14381     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14382     SetGameInfo();
14383     DisplayTwoMachinesTitle();
14384     firstMove = TRUE;
14385     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14386         onmove = &first;
14387     } else {
14388         onmove = &second;
14389     }
14390     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14391     SendToProgram(first.computerString, &first);
14392     if (first.sendName) {
14393       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14394       SendToProgram(buf, &first);
14395     }
14396     SendToProgram(second.computerString, &second);
14397     if (second.sendName) {
14398       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14399       SendToProgram(buf, &second);
14400     }
14401
14402     ResetClocks();
14403     if (!first.sendTime || !second.sendTime) {
14404         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14405         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14406     }
14407     if (onmove->sendTime) {
14408       if (onmove->useColors) {
14409         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14410       }
14411       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14412     }
14413     if (onmove->useColors) {
14414       SendToProgram(onmove->twoMachinesColor, onmove);
14415     }
14416     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14417 //    SendToProgram("go\n", onmove);
14418     onmove->maybeThinking = TRUE;
14419     SetMachineThinkingEnables();
14420
14421     StartClocks();
14422
14423     if(bookHit) { // [HGM] book: simulate book reply
14424         static char bookMove[MSG_SIZ]; // a bit generous?
14425
14426         programStats.nodes = programStats.depth = programStats.time =
14427         programStats.score = programStats.got_only_move = 0;
14428         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14429
14430         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14431         strcat(bookMove, bookHit);
14432         savedMessage = bookMove; // args for deferred call
14433         savedState = onmove;
14434         ScheduleDelayedEvent(DeferredBookMove, 1);
14435     }
14436 }
14437
14438 void
14439 TrainingEvent ()
14440 {
14441     if (gameMode == Training) {
14442       SetTrainingModeOff();
14443       gameMode = PlayFromGameFile;
14444       DisplayMessage("", _("Training mode off"));
14445     } else {
14446       gameMode = Training;
14447       animateTraining = appData.animate;
14448
14449       /* make sure we are not already at the end of the game */
14450       if (currentMove < forwardMostMove) {
14451         SetTrainingModeOn();
14452         DisplayMessage("", _("Training mode on"));
14453       } else {
14454         gameMode = PlayFromGameFile;
14455         DisplayError(_("Already at end of game"), 0);
14456       }
14457     }
14458     ModeHighlight();
14459 }
14460
14461 void
14462 IcsClientEvent ()
14463 {
14464     if (!appData.icsActive) return;
14465     switch (gameMode) {
14466       case IcsPlayingWhite:
14467       case IcsPlayingBlack:
14468       case IcsObserving:
14469       case IcsIdle:
14470       case BeginningOfGame:
14471       case IcsExamining:
14472         return;
14473
14474       case EditGame:
14475         break;
14476
14477       case EditPosition:
14478         EditPositionDone(TRUE);
14479         break;
14480
14481       case AnalyzeMode:
14482       case AnalyzeFile:
14483         ExitAnalyzeMode();
14484         break;
14485
14486       default:
14487         EditGameEvent();
14488         break;
14489     }
14490
14491     gameMode = IcsIdle;
14492     ModeHighlight();
14493     return;
14494 }
14495
14496 void
14497 EditGameEvent ()
14498 {
14499     int i;
14500
14501     switch (gameMode) {
14502       case Training:
14503         SetTrainingModeOff();
14504         break;
14505       case MachinePlaysWhite:
14506       case MachinePlaysBlack:
14507       case BeginningOfGame:
14508         SendToProgram("force\n", &first);
14509         SetUserThinkingEnables();
14510         break;
14511       case PlayFromGameFile:
14512         (void) StopLoadGameTimer();
14513         if (gameFileFP != NULL) {
14514             gameFileFP = NULL;
14515         }
14516         break;
14517       case EditPosition:
14518         EditPositionDone(TRUE);
14519         break;
14520       case AnalyzeMode:
14521       case AnalyzeFile:
14522         ExitAnalyzeMode();
14523         SendToProgram("force\n", &first);
14524         break;
14525       case TwoMachinesPlay:
14526         GameEnds(EndOfFile, NULL, GE_PLAYER);
14527         ResurrectChessProgram();
14528         SetUserThinkingEnables();
14529         break;
14530       case EndOfGame:
14531         ResurrectChessProgram();
14532         break;
14533       case IcsPlayingBlack:
14534       case IcsPlayingWhite:
14535         DisplayError(_("Warning: You are still playing a game"), 0);
14536         break;
14537       case IcsObserving:
14538         DisplayError(_("Warning: You are still observing a game"), 0);
14539         break;
14540       case IcsExamining:
14541         DisplayError(_("Warning: You are still examining a game"), 0);
14542         break;
14543       case IcsIdle:
14544         break;
14545       case EditGame:
14546       default:
14547         return;
14548     }
14549
14550     pausing = FALSE;
14551     StopClocks();
14552     first.offeredDraw = second.offeredDraw = 0;
14553
14554     if (gameMode == PlayFromGameFile) {
14555         whiteTimeRemaining = timeRemaining[0][currentMove];
14556         blackTimeRemaining = timeRemaining[1][currentMove];
14557         DisplayTitle("");
14558     }
14559
14560     if (gameMode == MachinePlaysWhite ||
14561         gameMode == MachinePlaysBlack ||
14562         gameMode == TwoMachinesPlay ||
14563         gameMode == EndOfGame) {
14564         i = forwardMostMove;
14565         while (i > currentMove) {
14566             SendToProgram("undo\n", &first);
14567             i--;
14568         }
14569         if(!adjustedClock) {
14570         whiteTimeRemaining = timeRemaining[0][currentMove];
14571         blackTimeRemaining = timeRemaining[1][currentMove];
14572         DisplayBothClocks();
14573         }
14574         if (whiteFlag || blackFlag) {
14575             whiteFlag = blackFlag = 0;
14576         }
14577         DisplayTitle("");
14578     }
14579
14580     gameMode = EditGame;
14581     ModeHighlight();
14582     SetGameInfo();
14583 }
14584
14585
14586 void
14587 EditPositionEvent ()
14588 {
14589     if (gameMode == EditPosition) {
14590         EditGameEvent();
14591         return;
14592     }
14593
14594     EditGameEvent();
14595     if (gameMode != EditGame) return;
14596
14597     gameMode = EditPosition;
14598     ModeHighlight();
14599     SetGameInfo();
14600     if (currentMove > 0)
14601       CopyBoard(boards[0], boards[currentMove]);
14602
14603     blackPlaysFirst = !WhiteOnMove(currentMove);
14604     ResetClocks();
14605     currentMove = forwardMostMove = backwardMostMove = 0;
14606     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14607     DisplayMove(-1);
14608     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14609 }
14610
14611 void
14612 ExitAnalyzeMode ()
14613 {
14614     /* [DM] icsEngineAnalyze - possible call from other functions */
14615     if (appData.icsEngineAnalyze) {
14616         appData.icsEngineAnalyze = FALSE;
14617
14618         DisplayMessage("",_("Close ICS engine analyze..."));
14619     }
14620     if (first.analysisSupport && first.analyzing) {
14621       SendToBoth("exit\n");
14622       first.analyzing = second.analyzing = FALSE;
14623     }
14624     thinkOutput[0] = NULLCHAR;
14625 }
14626
14627 void
14628 EditPositionDone (Boolean fakeRights)
14629 {
14630     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14631
14632     startedFromSetupPosition = TRUE;
14633     InitChessProgram(&first, FALSE);
14634     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14635       boards[0][EP_STATUS] = EP_NONE;
14636       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14637       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14638         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14639         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14640       } else boards[0][CASTLING][2] = NoRights;
14641       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14642         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14643         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14644       } else boards[0][CASTLING][5] = NoRights;
14645       if(gameInfo.variant == VariantSChess) {
14646         int i;
14647         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14648           boards[0][VIRGIN][i] = 0;
14649           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14650           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14651         }
14652       }
14653     }
14654     SendToProgram("force\n", &first);
14655     if (blackPlaysFirst) {
14656         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14657         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14658         currentMove = forwardMostMove = backwardMostMove = 1;
14659         CopyBoard(boards[1], boards[0]);
14660     } else {
14661         currentMove = forwardMostMove = backwardMostMove = 0;
14662     }
14663     SendBoard(&first, forwardMostMove);
14664     if (appData.debugMode) {
14665         fprintf(debugFP, "EditPosDone\n");
14666     }
14667     DisplayTitle("");
14668     DisplayMessage("", "");
14669     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14670     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14671     gameMode = EditGame;
14672     ModeHighlight();
14673     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14674     ClearHighlights(); /* [AS] */
14675 }
14676
14677 /* Pause for `ms' milliseconds */
14678 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14679 void
14680 TimeDelay (long ms)
14681 {
14682     TimeMark m1, m2;
14683
14684     GetTimeMark(&m1);
14685     do {
14686         GetTimeMark(&m2);
14687     } while (SubtractTimeMarks(&m2, &m1) < ms);
14688 }
14689
14690 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14691 void
14692 SendMultiLineToICS (char *buf)
14693 {
14694     char temp[MSG_SIZ+1], *p;
14695     int len;
14696
14697     len = strlen(buf);
14698     if (len > MSG_SIZ)
14699       len = MSG_SIZ;
14700
14701     strncpy(temp, buf, len);
14702     temp[len] = 0;
14703
14704     p = temp;
14705     while (*p) {
14706         if (*p == '\n' || *p == '\r')
14707           *p = ' ';
14708         ++p;
14709     }
14710
14711     strcat(temp, "\n");
14712     SendToICS(temp);
14713     SendToPlayer(temp, strlen(temp));
14714 }
14715
14716 void
14717 SetWhiteToPlayEvent ()
14718 {
14719     if (gameMode == EditPosition) {
14720         blackPlaysFirst = FALSE;
14721         DisplayBothClocks();    /* works because currentMove is 0 */
14722     } else if (gameMode == IcsExamining) {
14723         SendToICS(ics_prefix);
14724         SendToICS("tomove white\n");
14725     }
14726 }
14727
14728 void
14729 SetBlackToPlayEvent ()
14730 {
14731     if (gameMode == EditPosition) {
14732         blackPlaysFirst = TRUE;
14733         currentMove = 1;        /* kludge */
14734         DisplayBothClocks();
14735         currentMove = 0;
14736     } else if (gameMode == IcsExamining) {
14737         SendToICS(ics_prefix);
14738         SendToICS("tomove black\n");
14739     }
14740 }
14741
14742 void
14743 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14744 {
14745     char buf[MSG_SIZ];
14746     ChessSquare piece = boards[0][y][x];
14747     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14748     static int lastVariant;
14749
14750     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14751
14752     switch (selection) {
14753       case ClearBoard:
14754         CopyBoard(currentBoard, boards[0]);
14755         CopyBoard(menuBoard, initialPosition);
14756         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14757             SendToICS(ics_prefix);
14758             SendToICS("bsetup clear\n");
14759         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14760             SendToICS(ics_prefix);
14761             SendToICS("clearboard\n");
14762         } else {
14763             int nonEmpty = 0;
14764             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14765                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14766                 for (y = 0; y < BOARD_HEIGHT; y++) {
14767                     if (gameMode == IcsExamining) {
14768                         if (boards[currentMove][y][x] != EmptySquare) {
14769                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14770                                     AAA + x, ONE + y);
14771                             SendToICS(buf);
14772                         }
14773                     } else {
14774                         if(boards[0][y][x] != p) nonEmpty++;
14775                         boards[0][y][x] = p;
14776                     }
14777                 }
14778                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14779             }
14780             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14781                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14782                     ChessSquare p = menuBoard[0][x];
14783                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14784                     p = menuBoard[BOARD_HEIGHT-1][x];
14785                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14786                 }
14787                 DisplayMessage("Clicking clock again restores position", "");
14788                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14789                 if(!nonEmpty) { // asked to clear an empty board
14790                     CopyBoard(boards[0], menuBoard);
14791                 } else
14792                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14793                     CopyBoard(boards[0], initialPosition);
14794                 } else
14795                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14796                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14797                     CopyBoard(boards[0], erasedBoard);
14798                 } else
14799                     CopyBoard(erasedBoard, currentBoard);
14800
14801             }
14802         }
14803         if (gameMode == EditPosition) {
14804             DrawPosition(FALSE, boards[0]);
14805         }
14806         break;
14807
14808       case WhitePlay:
14809         SetWhiteToPlayEvent();
14810         break;
14811
14812       case BlackPlay:
14813         SetBlackToPlayEvent();
14814         break;
14815
14816       case EmptySquare:
14817         if (gameMode == IcsExamining) {
14818             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14819             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14820             SendToICS(buf);
14821         } else {
14822             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14823                 if(x == BOARD_LEFT-2) {
14824                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14825                     boards[0][y][1] = 0;
14826                 } else
14827                 if(x == BOARD_RGHT+1) {
14828                     if(y >= gameInfo.holdingsSize) break;
14829                     boards[0][y][BOARD_WIDTH-2] = 0;
14830                 } else break;
14831             }
14832             boards[0][y][x] = EmptySquare;
14833             DrawPosition(FALSE, boards[0]);
14834         }
14835         break;
14836
14837       case PromotePiece:
14838         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14839            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14840             selection = (ChessSquare) (PROMOTED piece);
14841         } else if(piece == EmptySquare) selection = WhiteSilver;
14842         else selection = (ChessSquare)((int)piece - 1);
14843         goto defaultlabel;
14844
14845       case DemotePiece:
14846         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14847            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14848             selection = (ChessSquare) (DEMOTED piece);
14849         } else if(piece == EmptySquare) selection = BlackSilver;
14850         else selection = (ChessSquare)((int)piece + 1);
14851         goto defaultlabel;
14852
14853       case WhiteQueen:
14854       case BlackQueen:
14855         if(gameInfo.variant == VariantShatranj ||
14856            gameInfo.variant == VariantXiangqi  ||
14857            gameInfo.variant == VariantCourier  ||
14858            gameInfo.variant == VariantASEAN    ||
14859            gameInfo.variant == VariantMakruk     )
14860             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14861         goto defaultlabel;
14862
14863       case WhiteKing:
14864       case BlackKing:
14865         if(gameInfo.variant == VariantXiangqi)
14866             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14867         if(gameInfo.variant == VariantKnightmate)
14868             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14869       default:
14870         defaultlabel:
14871         if (gameMode == IcsExamining) {
14872             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14873             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14874                      PieceToChar(selection), AAA + x, ONE + y);
14875             SendToICS(buf);
14876         } else {
14877             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14878                 int n;
14879                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14880                     n = PieceToNumber(selection - BlackPawn);
14881                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14882                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14883                     boards[0][BOARD_HEIGHT-1-n][1]++;
14884                 } else
14885                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14886                     n = PieceToNumber(selection);
14887                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14888                     boards[0][n][BOARD_WIDTH-1] = selection;
14889                     boards[0][n][BOARD_WIDTH-2]++;
14890                 }
14891             } else
14892             boards[0][y][x] = selection;
14893             DrawPosition(TRUE, boards[0]);
14894             ClearHighlights();
14895             fromX = fromY = -1;
14896         }
14897         break;
14898     }
14899 }
14900
14901
14902 void
14903 DropMenuEvent (ChessSquare selection, int x, int y)
14904 {
14905     ChessMove moveType;
14906
14907     switch (gameMode) {
14908       case IcsPlayingWhite:
14909       case MachinePlaysBlack:
14910         if (!WhiteOnMove(currentMove)) {
14911             DisplayMoveError(_("It is Black's turn"));
14912             return;
14913         }
14914         moveType = WhiteDrop;
14915         break;
14916       case IcsPlayingBlack:
14917       case MachinePlaysWhite:
14918         if (WhiteOnMove(currentMove)) {
14919             DisplayMoveError(_("It is White's turn"));
14920             return;
14921         }
14922         moveType = BlackDrop;
14923         break;
14924       case EditGame:
14925         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14926         break;
14927       default:
14928         return;
14929     }
14930
14931     if (moveType == BlackDrop && selection < BlackPawn) {
14932       selection = (ChessSquare) ((int) selection
14933                                  + (int) BlackPawn - (int) WhitePawn);
14934     }
14935     if (boards[currentMove][y][x] != EmptySquare) {
14936         DisplayMoveError(_("That square is occupied"));
14937         return;
14938     }
14939
14940     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14941 }
14942
14943 void
14944 AcceptEvent ()
14945 {
14946     /* Accept a pending offer of any kind from opponent */
14947
14948     if (appData.icsActive) {
14949         SendToICS(ics_prefix);
14950         SendToICS("accept\n");
14951     } else if (cmailMsgLoaded) {
14952         if (currentMove == cmailOldMove &&
14953             commentList[cmailOldMove] != NULL &&
14954             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14955                    "Black offers a draw" : "White offers a draw")) {
14956             TruncateGame();
14957             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14958             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14959         } else {
14960             DisplayError(_("There is no pending offer on this move"), 0);
14961             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14962         }
14963     } else {
14964         /* Not used for offers from chess program */
14965     }
14966 }
14967
14968 void
14969 DeclineEvent ()
14970 {
14971     /* Decline a pending offer of any kind from opponent */
14972
14973     if (appData.icsActive) {
14974         SendToICS(ics_prefix);
14975         SendToICS("decline\n");
14976     } else if (cmailMsgLoaded) {
14977         if (currentMove == cmailOldMove &&
14978             commentList[cmailOldMove] != NULL &&
14979             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14980                    "Black offers a draw" : "White offers a draw")) {
14981 #ifdef NOTDEF
14982             AppendComment(cmailOldMove, "Draw declined", TRUE);
14983             DisplayComment(cmailOldMove - 1, "Draw declined");
14984 #endif /*NOTDEF*/
14985         } else {
14986             DisplayError(_("There is no pending offer on this move"), 0);
14987         }
14988     } else {
14989         /* Not used for offers from chess program */
14990     }
14991 }
14992
14993 void
14994 RematchEvent ()
14995 {
14996     /* Issue ICS rematch command */
14997     if (appData.icsActive) {
14998         SendToICS(ics_prefix);
14999         SendToICS("rematch\n");
15000     }
15001 }
15002
15003 void
15004 CallFlagEvent ()
15005 {
15006     /* Call your opponent's flag (claim a win on time) */
15007     if (appData.icsActive) {
15008         SendToICS(ics_prefix);
15009         SendToICS("flag\n");
15010     } else {
15011         switch (gameMode) {
15012           default:
15013             return;
15014           case MachinePlaysWhite:
15015             if (whiteFlag) {
15016                 if (blackFlag)
15017                   GameEnds(GameIsDrawn, "Both players ran out of time",
15018                            GE_PLAYER);
15019                 else
15020                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15021             } else {
15022                 DisplayError(_("Your opponent is not out of time"), 0);
15023             }
15024             break;
15025           case MachinePlaysBlack:
15026             if (blackFlag) {
15027                 if (whiteFlag)
15028                   GameEnds(GameIsDrawn, "Both players ran out of time",
15029                            GE_PLAYER);
15030                 else
15031                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15032             } else {
15033                 DisplayError(_("Your opponent is not out of time"), 0);
15034             }
15035             break;
15036         }
15037     }
15038 }
15039
15040 void
15041 ClockClick (int which)
15042 {       // [HGM] code moved to back-end from winboard.c
15043         if(which) { // black clock
15044           if (gameMode == EditPosition || gameMode == IcsExamining) {
15045             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15046             SetBlackToPlayEvent();
15047           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15048           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15049           } else if (shiftKey) {
15050             AdjustClock(which, -1);
15051           } else if (gameMode == IcsPlayingWhite ||
15052                      gameMode == MachinePlaysBlack) {
15053             CallFlagEvent();
15054           }
15055         } else { // white clock
15056           if (gameMode == EditPosition || gameMode == IcsExamining) {
15057             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15058             SetWhiteToPlayEvent();
15059           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15060           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15061           } else if (shiftKey) {
15062             AdjustClock(which, -1);
15063           } else if (gameMode == IcsPlayingBlack ||
15064                    gameMode == MachinePlaysWhite) {
15065             CallFlagEvent();
15066           }
15067         }
15068 }
15069
15070 void
15071 DrawEvent ()
15072 {
15073     /* Offer draw or accept pending draw offer from opponent */
15074
15075     if (appData.icsActive) {
15076         /* Note: tournament rules require draw offers to be
15077            made after you make your move but before you punch
15078            your clock.  Currently ICS doesn't let you do that;
15079            instead, you immediately punch your clock after making
15080            a move, but you can offer a draw at any time. */
15081
15082         SendToICS(ics_prefix);
15083         SendToICS("draw\n");
15084         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15085     } else if (cmailMsgLoaded) {
15086         if (currentMove == cmailOldMove &&
15087             commentList[cmailOldMove] != NULL &&
15088             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15089                    "Black offers a draw" : "White offers a draw")) {
15090             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15091             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15092         } else if (currentMove == cmailOldMove + 1) {
15093             char *offer = WhiteOnMove(cmailOldMove) ?
15094               "White offers a draw" : "Black offers a draw";
15095             AppendComment(currentMove, offer, TRUE);
15096             DisplayComment(currentMove - 1, offer);
15097             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15098         } else {
15099             DisplayError(_("You must make your move before offering a draw"), 0);
15100             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15101         }
15102     } else if (first.offeredDraw) {
15103         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15104     } else {
15105         if (first.sendDrawOffers) {
15106             SendToProgram("draw\n", &first);
15107             userOfferedDraw = TRUE;
15108         }
15109     }
15110 }
15111
15112 void
15113 AdjournEvent ()
15114 {
15115     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15116
15117     if (appData.icsActive) {
15118         SendToICS(ics_prefix);
15119         SendToICS("adjourn\n");
15120     } else {
15121         /* Currently GNU Chess doesn't offer or accept Adjourns */
15122     }
15123 }
15124
15125
15126 void
15127 AbortEvent ()
15128 {
15129     /* Offer Abort or accept pending Abort offer from opponent */
15130
15131     if (appData.icsActive) {
15132         SendToICS(ics_prefix);
15133         SendToICS("abort\n");
15134     } else {
15135         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15136     }
15137 }
15138
15139 void
15140 ResignEvent ()
15141 {
15142     /* Resign.  You can do this even if it's not your turn. */
15143
15144     if (appData.icsActive) {
15145         SendToICS(ics_prefix);
15146         SendToICS("resign\n");
15147     } else {
15148         switch (gameMode) {
15149           case MachinePlaysWhite:
15150             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15151             break;
15152           case MachinePlaysBlack:
15153             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15154             break;
15155           case EditGame:
15156             if (cmailMsgLoaded) {
15157                 TruncateGame();
15158                 if (WhiteOnMove(cmailOldMove)) {
15159                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15160                 } else {
15161                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15162                 }
15163                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15164             }
15165             break;
15166           default:
15167             break;
15168         }
15169     }
15170 }
15171
15172
15173 void
15174 StopObservingEvent ()
15175 {
15176     /* Stop observing current games */
15177     SendToICS(ics_prefix);
15178     SendToICS("unobserve\n");
15179 }
15180
15181 void
15182 StopExaminingEvent ()
15183 {
15184     /* Stop observing current game */
15185     SendToICS(ics_prefix);
15186     SendToICS("unexamine\n");
15187 }
15188
15189 void
15190 ForwardInner (int target)
15191 {
15192     int limit; int oldSeekGraphUp = seekGraphUp;
15193
15194     if (appData.debugMode)
15195         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15196                 target, currentMove, forwardMostMove);
15197
15198     if (gameMode == EditPosition)
15199       return;
15200
15201     seekGraphUp = FALSE;
15202     MarkTargetSquares(1);
15203
15204     if (gameMode == PlayFromGameFile && !pausing)
15205       PauseEvent();
15206
15207     if (gameMode == IcsExamining && pausing)
15208       limit = pauseExamForwardMostMove;
15209     else
15210       limit = forwardMostMove;
15211
15212     if (target > limit) target = limit;
15213
15214     if (target > 0 && moveList[target - 1][0]) {
15215         int fromX, fromY, toX, toY;
15216         toX = moveList[target - 1][2] - AAA;
15217         toY = moveList[target - 1][3] - ONE;
15218         if (moveList[target - 1][1] == '@') {
15219             if (appData.highlightLastMove) {
15220                 SetHighlights(-1, -1, toX, toY);
15221             }
15222         } else {
15223             fromX = moveList[target - 1][0] - AAA;
15224             fromY = moveList[target - 1][1] - ONE;
15225             if (target == currentMove + 1) {
15226                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15227             }
15228             if (appData.highlightLastMove) {
15229                 SetHighlights(fromX, fromY, toX, toY);
15230             }
15231         }
15232     }
15233     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15234         gameMode == Training || gameMode == PlayFromGameFile ||
15235         gameMode == AnalyzeFile) {
15236         while (currentMove < target) {
15237             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15238             SendMoveToProgram(currentMove++, &first);
15239         }
15240     } else {
15241         currentMove = target;
15242     }
15243
15244     if (gameMode == EditGame || gameMode == EndOfGame) {
15245         whiteTimeRemaining = timeRemaining[0][currentMove];
15246         blackTimeRemaining = timeRemaining[1][currentMove];
15247     }
15248     DisplayBothClocks();
15249     DisplayMove(currentMove - 1);
15250     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15251     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15252     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15253         DisplayComment(currentMove - 1, commentList[currentMove]);
15254     }
15255     ClearMap(); // [HGM] exclude: invalidate map
15256 }
15257
15258
15259 void
15260 ForwardEvent ()
15261 {
15262     if (gameMode == IcsExamining && !pausing) {
15263         SendToICS(ics_prefix);
15264         SendToICS("forward\n");
15265     } else {
15266         ForwardInner(currentMove + 1);
15267     }
15268 }
15269
15270 void
15271 ToEndEvent ()
15272 {
15273     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15274         /* to optimze, we temporarily turn off analysis mode while we feed
15275          * the remaining moves to the engine. Otherwise we get analysis output
15276          * after each move.
15277          */
15278         if (first.analysisSupport) {
15279           SendToProgram("exit\nforce\n", &first);
15280           first.analyzing = FALSE;
15281         }
15282     }
15283
15284     if (gameMode == IcsExamining && !pausing) {
15285         SendToICS(ics_prefix);
15286         SendToICS("forward 999999\n");
15287     } else {
15288         ForwardInner(forwardMostMove);
15289     }
15290
15291     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15292         /* we have fed all the moves, so reactivate analysis mode */
15293         SendToProgram("analyze\n", &first);
15294         first.analyzing = TRUE;
15295         /*first.maybeThinking = TRUE;*/
15296         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15297     }
15298 }
15299
15300 void
15301 BackwardInner (int target)
15302 {
15303     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15304
15305     if (appData.debugMode)
15306         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15307                 target, currentMove, forwardMostMove);
15308
15309     if (gameMode == EditPosition) return;
15310     seekGraphUp = FALSE;
15311     MarkTargetSquares(1);
15312     if (currentMove <= backwardMostMove) {
15313         ClearHighlights();
15314         DrawPosition(full_redraw, boards[currentMove]);
15315         return;
15316     }
15317     if (gameMode == PlayFromGameFile && !pausing)
15318       PauseEvent();
15319
15320     if (moveList[target][0]) {
15321         int fromX, fromY, toX, toY;
15322         toX = moveList[target][2] - AAA;
15323         toY = moveList[target][3] - ONE;
15324         if (moveList[target][1] == '@') {
15325             if (appData.highlightLastMove) {
15326                 SetHighlights(-1, -1, toX, toY);
15327             }
15328         } else {
15329             fromX = moveList[target][0] - AAA;
15330             fromY = moveList[target][1] - ONE;
15331             if (target == currentMove - 1) {
15332                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15333             }
15334             if (appData.highlightLastMove) {
15335                 SetHighlights(fromX, fromY, toX, toY);
15336             }
15337         }
15338     }
15339     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15340         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15341         while (currentMove > target) {
15342             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15343                 // null move cannot be undone. Reload program with move history before it.
15344                 int i;
15345                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15346                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15347                 }
15348                 SendBoard(&first, i);
15349               if(second.analyzing) SendBoard(&second, i);
15350                 for(currentMove=i; currentMove<target; currentMove++) {
15351                     SendMoveToProgram(currentMove, &first);
15352                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15353                 }
15354                 break;
15355             }
15356             SendToBoth("undo\n");
15357             currentMove--;
15358         }
15359     } else {
15360         currentMove = target;
15361     }
15362
15363     if (gameMode == EditGame || gameMode == EndOfGame) {
15364         whiteTimeRemaining = timeRemaining[0][currentMove];
15365         blackTimeRemaining = timeRemaining[1][currentMove];
15366     }
15367     DisplayBothClocks();
15368     DisplayMove(currentMove - 1);
15369     DrawPosition(full_redraw, boards[currentMove]);
15370     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15371     // [HGM] PV info: routine tests if comment empty
15372     DisplayComment(currentMove - 1, commentList[currentMove]);
15373     ClearMap(); // [HGM] exclude: invalidate map
15374 }
15375
15376 void
15377 BackwardEvent ()
15378 {
15379     if (gameMode == IcsExamining && !pausing) {
15380         SendToICS(ics_prefix);
15381         SendToICS("backward\n");
15382     } else {
15383         BackwardInner(currentMove - 1);
15384     }
15385 }
15386
15387 void
15388 ToStartEvent ()
15389 {
15390     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15391         /* to optimize, we temporarily turn off analysis mode while we undo
15392          * all the moves. Otherwise we get analysis output after each undo.
15393          */
15394         if (first.analysisSupport) {
15395           SendToProgram("exit\nforce\n", &first);
15396           first.analyzing = FALSE;
15397         }
15398     }
15399
15400     if (gameMode == IcsExamining && !pausing) {
15401         SendToICS(ics_prefix);
15402         SendToICS("backward 999999\n");
15403     } else {
15404         BackwardInner(backwardMostMove);
15405     }
15406
15407     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15408         /* we have fed all the moves, so reactivate analysis mode */
15409         SendToProgram("analyze\n", &first);
15410         first.analyzing = TRUE;
15411         /*first.maybeThinking = TRUE;*/
15412         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15413     }
15414 }
15415
15416 void
15417 ToNrEvent (int to)
15418 {
15419   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15420   if (to >= forwardMostMove) to = forwardMostMove;
15421   if (to <= backwardMostMove) to = backwardMostMove;
15422   if (to < currentMove) {
15423     BackwardInner(to);
15424   } else {
15425     ForwardInner(to);
15426   }
15427 }
15428
15429 void
15430 RevertEvent (Boolean annotate)
15431 {
15432     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15433         return;
15434     }
15435     if (gameMode != IcsExamining) {
15436         DisplayError(_("You are not examining a game"), 0);
15437         return;
15438     }
15439     if (pausing) {
15440         DisplayError(_("You can't revert while pausing"), 0);
15441         return;
15442     }
15443     SendToICS(ics_prefix);
15444     SendToICS("revert\n");
15445 }
15446
15447 void
15448 RetractMoveEvent ()
15449 {
15450     switch (gameMode) {
15451       case MachinePlaysWhite:
15452       case MachinePlaysBlack:
15453         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15454             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15455             return;
15456         }
15457         if (forwardMostMove < 2) return;
15458         currentMove = forwardMostMove = forwardMostMove - 2;
15459         whiteTimeRemaining = timeRemaining[0][currentMove];
15460         blackTimeRemaining = timeRemaining[1][currentMove];
15461         DisplayBothClocks();
15462         DisplayMove(currentMove - 1);
15463         ClearHighlights();/*!! could figure this out*/
15464         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15465         SendToProgram("remove\n", &first);
15466         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15467         break;
15468
15469       case BeginningOfGame:
15470       default:
15471         break;
15472
15473       case IcsPlayingWhite:
15474       case IcsPlayingBlack:
15475         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15476             SendToICS(ics_prefix);
15477             SendToICS("takeback 2\n");
15478         } else {
15479             SendToICS(ics_prefix);
15480             SendToICS("takeback 1\n");
15481         }
15482         break;
15483     }
15484 }
15485
15486 void
15487 MoveNowEvent ()
15488 {
15489     ChessProgramState *cps;
15490
15491     switch (gameMode) {
15492       case MachinePlaysWhite:
15493         if (!WhiteOnMove(forwardMostMove)) {
15494             DisplayError(_("It is your turn"), 0);
15495             return;
15496         }
15497         cps = &first;
15498         break;
15499       case MachinePlaysBlack:
15500         if (WhiteOnMove(forwardMostMove)) {
15501             DisplayError(_("It is your turn"), 0);
15502             return;
15503         }
15504         cps = &first;
15505         break;
15506       case TwoMachinesPlay:
15507         if (WhiteOnMove(forwardMostMove) ==
15508             (first.twoMachinesColor[0] == 'w')) {
15509             cps = &first;
15510         } else {
15511             cps = &second;
15512         }
15513         break;
15514       case BeginningOfGame:
15515       default:
15516         return;
15517     }
15518     SendToProgram("?\n", cps);
15519 }
15520
15521 void
15522 TruncateGameEvent ()
15523 {
15524     EditGameEvent();
15525     if (gameMode != EditGame) return;
15526     TruncateGame();
15527 }
15528
15529 void
15530 TruncateGame ()
15531 {
15532     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15533     if (forwardMostMove > currentMove) {
15534         if (gameInfo.resultDetails != NULL) {
15535             free(gameInfo.resultDetails);
15536             gameInfo.resultDetails = NULL;
15537             gameInfo.result = GameUnfinished;
15538         }
15539         forwardMostMove = currentMove;
15540         HistorySet(parseList, backwardMostMove, forwardMostMove,
15541                    currentMove-1);
15542     }
15543 }
15544
15545 void
15546 HintEvent ()
15547 {
15548     if (appData.noChessProgram) return;
15549     switch (gameMode) {
15550       case MachinePlaysWhite:
15551         if (WhiteOnMove(forwardMostMove)) {
15552             DisplayError(_("Wait until your turn."), 0);
15553             return;
15554         }
15555         break;
15556       case BeginningOfGame:
15557       case MachinePlaysBlack:
15558         if (!WhiteOnMove(forwardMostMove)) {
15559             DisplayError(_("Wait until your turn."), 0);
15560             return;
15561         }
15562         break;
15563       default:
15564         DisplayError(_("No hint available"), 0);
15565         return;
15566     }
15567     SendToProgram("hint\n", &first);
15568     hintRequested = TRUE;
15569 }
15570
15571 void
15572 CreateBookEvent ()
15573 {
15574     ListGame * lg = (ListGame *) gameList.head;
15575     FILE *f, *g;
15576     int nItem;
15577     static int secondTime = FALSE;
15578
15579     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15580         DisplayError(_("Game list not loaded or empty"), 0);
15581         return;
15582     }
15583
15584     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15585         fclose(g);
15586         secondTime++;
15587         DisplayNote(_("Book file exists! Try again for overwrite."));
15588         return;
15589     }
15590
15591     creatingBook = TRUE;
15592     secondTime = FALSE;
15593
15594     /* Get list size */
15595     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15596         LoadGame(f, nItem, "", TRUE);
15597         AddGameToBook(TRUE);
15598         lg = (ListGame *) lg->node.succ;
15599     }
15600
15601     creatingBook = FALSE;
15602     FlushBook();
15603 }
15604
15605 void
15606 BookEvent ()
15607 {
15608     if (appData.noChessProgram) return;
15609     switch (gameMode) {
15610       case MachinePlaysWhite:
15611         if (WhiteOnMove(forwardMostMove)) {
15612             DisplayError(_("Wait until your turn."), 0);
15613             return;
15614         }
15615         break;
15616       case BeginningOfGame:
15617       case MachinePlaysBlack:
15618         if (!WhiteOnMove(forwardMostMove)) {
15619             DisplayError(_("Wait until your turn."), 0);
15620             return;
15621         }
15622         break;
15623       case EditPosition:
15624         EditPositionDone(TRUE);
15625         break;
15626       case TwoMachinesPlay:
15627         return;
15628       default:
15629         break;
15630     }
15631     SendToProgram("bk\n", &first);
15632     bookOutput[0] = NULLCHAR;
15633     bookRequested = TRUE;
15634 }
15635
15636 void
15637 AboutGameEvent ()
15638 {
15639     char *tags = PGNTags(&gameInfo);
15640     TagsPopUp(tags, CmailMsg());
15641     free(tags);
15642 }
15643
15644 /* end button procedures */
15645
15646 void
15647 PrintPosition (FILE *fp, int move)
15648 {
15649     int i, j;
15650
15651     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15652         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15653             char c = PieceToChar(boards[move][i][j]);
15654             fputc(c == 'x' ? '.' : c, fp);
15655             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15656         }
15657     }
15658     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15659       fprintf(fp, "white to play\n");
15660     else
15661       fprintf(fp, "black to play\n");
15662 }
15663
15664 void
15665 PrintOpponents (FILE *fp)
15666 {
15667     if (gameInfo.white != NULL) {
15668         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15669     } else {
15670         fprintf(fp, "\n");
15671     }
15672 }
15673
15674 /* Find last component of program's own name, using some heuristics */
15675 void
15676 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15677 {
15678     char *p, *q, c;
15679     int local = (strcmp(host, "localhost") == 0);
15680     while (!local && (p = strchr(prog, ';')) != NULL) {
15681         p++;
15682         while (*p == ' ') p++;
15683         prog = p;
15684     }
15685     if (*prog == '"' || *prog == '\'') {
15686         q = strchr(prog + 1, *prog);
15687     } else {
15688         q = strchr(prog, ' ');
15689     }
15690     if (q == NULL) q = prog + strlen(prog);
15691     p = q;
15692     while (p >= prog && *p != '/' && *p != '\\') p--;
15693     p++;
15694     if(p == prog && *p == '"') p++;
15695     c = *q; *q = 0;
15696     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15697     memcpy(buf, p, q - p);
15698     buf[q - p] = NULLCHAR;
15699     if (!local) {
15700         strcat(buf, "@");
15701         strcat(buf, host);
15702     }
15703 }
15704
15705 char *
15706 TimeControlTagValue ()
15707 {
15708     char buf[MSG_SIZ];
15709     if (!appData.clockMode) {
15710       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15711     } else if (movesPerSession > 0) {
15712       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15713     } else if (timeIncrement == 0) {
15714       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15715     } else {
15716       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15717     }
15718     return StrSave(buf);
15719 }
15720
15721 void
15722 SetGameInfo ()
15723 {
15724     /* This routine is used only for certain modes */
15725     VariantClass v = gameInfo.variant;
15726     ChessMove r = GameUnfinished;
15727     char *p = NULL;
15728
15729     if(keepInfo) return;
15730
15731     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15732         r = gameInfo.result;
15733         p = gameInfo.resultDetails;
15734         gameInfo.resultDetails = NULL;
15735     }
15736     ClearGameInfo(&gameInfo);
15737     gameInfo.variant = v;
15738
15739     switch (gameMode) {
15740       case MachinePlaysWhite:
15741         gameInfo.event = StrSave( appData.pgnEventHeader );
15742         gameInfo.site = StrSave(HostName());
15743         gameInfo.date = PGNDate();
15744         gameInfo.round = StrSave("-");
15745         gameInfo.white = StrSave(first.tidy);
15746         gameInfo.black = StrSave(UserName());
15747         gameInfo.timeControl = TimeControlTagValue();
15748         break;
15749
15750       case MachinePlaysBlack:
15751         gameInfo.event = StrSave( appData.pgnEventHeader );
15752         gameInfo.site = StrSave(HostName());
15753         gameInfo.date = PGNDate();
15754         gameInfo.round = StrSave("-");
15755         gameInfo.white = StrSave(UserName());
15756         gameInfo.black = StrSave(first.tidy);
15757         gameInfo.timeControl = TimeControlTagValue();
15758         break;
15759
15760       case TwoMachinesPlay:
15761         gameInfo.event = StrSave( appData.pgnEventHeader );
15762         gameInfo.site = StrSave(HostName());
15763         gameInfo.date = PGNDate();
15764         if (roundNr > 0) {
15765             char buf[MSG_SIZ];
15766             snprintf(buf, MSG_SIZ, "%d", roundNr);
15767             gameInfo.round = StrSave(buf);
15768         } else {
15769             gameInfo.round = StrSave("-");
15770         }
15771         if (first.twoMachinesColor[0] == 'w') {
15772             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15773             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15774         } else {
15775             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15776             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15777         }
15778         gameInfo.timeControl = TimeControlTagValue();
15779         break;
15780
15781       case EditGame:
15782         gameInfo.event = StrSave("Edited game");
15783         gameInfo.site = StrSave(HostName());
15784         gameInfo.date = PGNDate();
15785         gameInfo.round = StrSave("-");
15786         gameInfo.white = StrSave("-");
15787         gameInfo.black = StrSave("-");
15788         gameInfo.result = r;
15789         gameInfo.resultDetails = p;
15790         break;
15791
15792       case EditPosition:
15793         gameInfo.event = StrSave("Edited position");
15794         gameInfo.site = StrSave(HostName());
15795         gameInfo.date = PGNDate();
15796         gameInfo.round = StrSave("-");
15797         gameInfo.white = StrSave("-");
15798         gameInfo.black = StrSave("-");
15799         break;
15800
15801       case IcsPlayingWhite:
15802       case IcsPlayingBlack:
15803       case IcsObserving:
15804       case IcsExamining:
15805         break;
15806
15807       case PlayFromGameFile:
15808         gameInfo.event = StrSave("Game from non-PGN file");
15809         gameInfo.site = StrSave(HostName());
15810         gameInfo.date = PGNDate();
15811         gameInfo.round = StrSave("-");
15812         gameInfo.white = StrSave("?");
15813         gameInfo.black = StrSave("?");
15814         break;
15815
15816       default:
15817         break;
15818     }
15819 }
15820
15821 void
15822 ReplaceComment (int index, char *text)
15823 {
15824     int len;
15825     char *p;
15826     float score;
15827
15828     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15829        pvInfoList[index-1].depth == len &&
15830        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15831        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15832     while (*text == '\n') text++;
15833     len = strlen(text);
15834     while (len > 0 && text[len - 1] == '\n') len--;
15835
15836     if (commentList[index] != NULL)
15837       free(commentList[index]);
15838
15839     if (len == 0) {
15840         commentList[index] = NULL;
15841         return;
15842     }
15843   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15844       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15845       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15846     commentList[index] = (char *) malloc(len + 2);
15847     strncpy(commentList[index], text, len);
15848     commentList[index][len] = '\n';
15849     commentList[index][len + 1] = NULLCHAR;
15850   } else {
15851     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15852     char *p;
15853     commentList[index] = (char *) malloc(len + 7);
15854     safeStrCpy(commentList[index], "{\n", 3);
15855     safeStrCpy(commentList[index]+2, text, len+1);
15856     commentList[index][len+2] = NULLCHAR;
15857     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15858     strcat(commentList[index], "\n}\n");
15859   }
15860 }
15861
15862 void
15863 CrushCRs (char *text)
15864 {
15865   char *p = text;
15866   char *q = text;
15867   char ch;
15868
15869   do {
15870     ch = *p++;
15871     if (ch == '\r') continue;
15872     *q++ = ch;
15873   } while (ch != '\0');
15874 }
15875
15876 void
15877 AppendComment (int index, char *text, Boolean addBraces)
15878 /* addBraces  tells if we should add {} */
15879 {
15880     int oldlen, len;
15881     char *old;
15882
15883 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15884     if(addBraces == 3) addBraces = 0; else // force appending literally
15885     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15886
15887     CrushCRs(text);
15888     while (*text == '\n') text++;
15889     len = strlen(text);
15890     while (len > 0 && text[len - 1] == '\n') len--;
15891     text[len] = NULLCHAR;
15892
15893     if (len == 0) return;
15894
15895     if (commentList[index] != NULL) {
15896       Boolean addClosingBrace = addBraces;
15897         old = commentList[index];
15898         oldlen = strlen(old);
15899         while(commentList[index][oldlen-1] ==  '\n')
15900           commentList[index][--oldlen] = NULLCHAR;
15901         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15902         safeStrCpy(commentList[index], old, oldlen + len + 6);
15903         free(old);
15904         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15905         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15906           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15907           while (*text == '\n') { text++; len--; }
15908           commentList[index][--oldlen] = NULLCHAR;
15909       }
15910         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15911         else          strcat(commentList[index], "\n");
15912         strcat(commentList[index], text);
15913         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15914         else          strcat(commentList[index], "\n");
15915     } else {
15916         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15917         if(addBraces)
15918           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15919         else commentList[index][0] = NULLCHAR;
15920         strcat(commentList[index], text);
15921         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15922         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15923     }
15924 }
15925
15926 static char *
15927 FindStr (char * text, char * sub_text)
15928 {
15929     char * result = strstr( text, sub_text );
15930
15931     if( result != NULL ) {
15932         result += strlen( sub_text );
15933     }
15934
15935     return result;
15936 }
15937
15938 /* [AS] Try to extract PV info from PGN comment */
15939 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15940 char *
15941 GetInfoFromComment (int index, char * text)
15942 {
15943     char * sep = text, *p;
15944
15945     if( text != NULL && index > 0 ) {
15946         int score = 0;
15947         int depth = 0;
15948         int time = -1, sec = 0, deci;
15949         char * s_eval = FindStr( text, "[%eval " );
15950         char * s_emt = FindStr( text, "[%emt " );
15951 #if 0
15952         if( s_eval != NULL || s_emt != NULL ) {
15953 #else
15954         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15955 #endif
15956             /* New style */
15957             char delim;
15958
15959             if( s_eval != NULL ) {
15960                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15961                     return text;
15962                 }
15963
15964                 if( delim != ']' ) {
15965                     return text;
15966                 }
15967             }
15968
15969             if( s_emt != NULL ) {
15970             }
15971                 return text;
15972         }
15973         else {
15974             /* We expect something like: [+|-]nnn.nn/dd */
15975             int score_lo = 0;
15976
15977             if(*text != '{') return text; // [HGM] braces: must be normal comment
15978
15979             sep = strchr( text, '/' );
15980             if( sep == NULL || sep < (text+4) ) {
15981                 return text;
15982             }
15983
15984             p = text;
15985             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15986             if(p[1] == '(') { // comment starts with PV
15987                p = strchr(p, ')'); // locate end of PV
15988                if(p == NULL || sep < p+5) return text;
15989                // at this point we have something like "{(.*) +0.23/6 ..."
15990                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15991                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15992                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15993             }
15994             time = -1; sec = -1; deci = -1;
15995             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15996                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15997                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15998                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15999                 return text;
16000             }
16001
16002             if( score_lo < 0 || score_lo >= 100 ) {
16003                 return text;
16004             }
16005
16006             if(sec >= 0) time = 600*time + 10*sec; else
16007             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16008
16009             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16010
16011             /* [HGM] PV time: now locate end of PV info */
16012             while( *++sep >= '0' && *sep <= '9'); // strip depth
16013             if(time >= 0)
16014             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16015             if(sec >= 0)
16016             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16017             if(deci >= 0)
16018             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16019             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16020         }
16021
16022         if( depth <= 0 ) {
16023             return text;
16024         }
16025
16026         if( time < 0 ) {
16027             time = -1;
16028         }
16029
16030         pvInfoList[index-1].depth = depth;
16031         pvInfoList[index-1].score = score;
16032         pvInfoList[index-1].time  = 10*time; // centi-sec
16033         if(*sep == '}') *sep = 0; else *--sep = '{';
16034         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16035     }
16036     return sep;
16037 }
16038
16039 void
16040 SendToProgram (char *message, ChessProgramState *cps)
16041 {
16042     int count, outCount, error;
16043     char buf[MSG_SIZ];
16044
16045     if (cps->pr == NoProc) return;
16046     Attention(cps);
16047
16048     if (appData.debugMode) {
16049         TimeMark now;
16050         GetTimeMark(&now);
16051         fprintf(debugFP, "%ld >%-6s: %s",
16052                 SubtractTimeMarks(&now, &programStartTime),
16053                 cps->which, message);
16054         if(serverFP)
16055             fprintf(serverFP, "%ld >%-6s: %s",
16056                 SubtractTimeMarks(&now, &programStartTime),
16057                 cps->which, message), fflush(serverFP);
16058     }
16059
16060     count = strlen(message);
16061     outCount = OutputToProcess(cps->pr, message, count, &error);
16062     if (outCount < count && !exiting
16063                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16064       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16065       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16066         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16067             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16068                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16069                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16070                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16071             } else {
16072                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16073                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16074                 gameInfo.result = res;
16075             }
16076             gameInfo.resultDetails = StrSave(buf);
16077         }
16078         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16079         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16080     }
16081 }
16082
16083 void
16084 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16085 {
16086     char *end_str;
16087     char buf[MSG_SIZ];
16088     ChessProgramState *cps = (ChessProgramState *)closure;
16089
16090     if (isr != cps->isr) return; /* Killed intentionally */
16091     if (count <= 0) {
16092         if (count == 0) {
16093             RemoveInputSource(cps->isr);
16094             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16095                     _(cps->which), cps->program);
16096             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16097             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16098                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16099                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16100                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16101                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16102                 } else {
16103                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16104                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16105                     gameInfo.result = res;
16106                 }
16107                 gameInfo.resultDetails = StrSave(buf);
16108             }
16109             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16110             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16111         } else {
16112             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16113                     _(cps->which), cps->program);
16114             RemoveInputSource(cps->isr);
16115
16116             /* [AS] Program is misbehaving badly... kill it */
16117             if( count == -2 ) {
16118                 DestroyChildProcess( cps->pr, 9 );
16119                 cps->pr = NoProc;
16120             }
16121
16122             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16123         }
16124         return;
16125     }
16126
16127     if ((end_str = strchr(message, '\r')) != NULL)
16128       *end_str = NULLCHAR;
16129     if ((end_str = strchr(message, '\n')) != NULL)
16130       *end_str = NULLCHAR;
16131
16132     if (appData.debugMode) {
16133         TimeMark now; int print = 1;
16134         char *quote = ""; char c; int i;
16135
16136         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16137                 char start = message[0];
16138                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16139                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16140                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16141                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16142                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16143                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16144                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16145                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16146                    sscanf(message, "hint: %c", &c)!=1 &&
16147                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16148                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16149                     print = (appData.engineComments >= 2);
16150                 }
16151                 message[0] = start; // restore original message
16152         }
16153         if(print) {
16154                 GetTimeMark(&now);
16155                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16156                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16157                         quote,
16158                         message);
16159                 if(serverFP)
16160                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16161                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16162                         quote,
16163                         message), fflush(serverFP);
16164         }
16165     }
16166
16167     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16168     if (appData.icsEngineAnalyze) {
16169         if (strstr(message, "whisper") != NULL ||
16170              strstr(message, "kibitz") != NULL ||
16171             strstr(message, "tellics") != NULL) return;
16172     }
16173
16174     HandleMachineMove(message, cps);
16175 }
16176
16177
16178 void
16179 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16180 {
16181     char buf[MSG_SIZ];
16182     int seconds;
16183
16184     if( timeControl_2 > 0 ) {
16185         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16186             tc = timeControl_2;
16187         }
16188     }
16189     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16190     inc /= cps->timeOdds;
16191     st  /= cps->timeOdds;
16192
16193     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16194
16195     if (st > 0) {
16196       /* Set exact time per move, normally using st command */
16197       if (cps->stKludge) {
16198         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16199         seconds = st % 60;
16200         if (seconds == 0) {
16201           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16202         } else {
16203           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16204         }
16205       } else {
16206         snprintf(buf, MSG_SIZ, "st %d\n", st);
16207       }
16208     } else {
16209       /* Set conventional or incremental time control, using level command */
16210       if (seconds == 0) {
16211         /* Note old gnuchess bug -- minutes:seconds used to not work.
16212            Fixed in later versions, but still avoid :seconds
16213            when seconds is 0. */
16214         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16215       } else {
16216         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16217                  seconds, inc/1000.);
16218       }
16219     }
16220     SendToProgram(buf, cps);
16221
16222     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16223     /* Orthogonally, limit search to given depth */
16224     if (sd > 0) {
16225       if (cps->sdKludge) {
16226         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16227       } else {
16228         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16229       }
16230       SendToProgram(buf, cps);
16231     }
16232
16233     if(cps->nps >= 0) { /* [HGM] nps */
16234         if(cps->supportsNPS == FALSE)
16235           cps->nps = -1; // don't use if engine explicitly says not supported!
16236         else {
16237           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16238           SendToProgram(buf, cps);
16239         }
16240     }
16241 }
16242
16243 ChessProgramState *
16244 WhitePlayer ()
16245 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16246 {
16247     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16248        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16249         return &second;
16250     return &first;
16251 }
16252
16253 void
16254 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16255 {
16256     char message[MSG_SIZ];
16257     long time, otime;
16258
16259     /* Note: this routine must be called when the clocks are stopped
16260        or when they have *just* been set or switched; otherwise
16261        it will be off by the time since the current tick started.
16262     */
16263     if (machineWhite) {
16264         time = whiteTimeRemaining / 10;
16265         otime = blackTimeRemaining / 10;
16266     } else {
16267         time = blackTimeRemaining / 10;
16268         otime = whiteTimeRemaining / 10;
16269     }
16270     /* [HGM] translate opponent's time by time-odds factor */
16271     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16272
16273     if (time <= 0) time = 1;
16274     if (otime <= 0) otime = 1;
16275
16276     snprintf(message, MSG_SIZ, "time %ld\n", time);
16277     SendToProgram(message, cps);
16278
16279     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16280     SendToProgram(message, cps);
16281 }
16282
16283 char *
16284 EngineDefinedVariant (ChessProgramState *cps, int n)
16285 {   // return name of n-th unknown variant that engine supports
16286     static char buf[MSG_SIZ];
16287     char *p, *s = cps->variants;
16288     if(!s) return NULL;
16289     do { // parse string from variants feature
16290       VariantClass v;
16291         p = strchr(s, ',');
16292         if(p) *p = NULLCHAR;
16293       v = StringToVariant(s);
16294       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16295         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16296             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16297         }
16298         if(p) *p++ = ',';
16299         if(n < 0) return buf;
16300     } while(s = p);
16301     return NULL;
16302 }
16303
16304 int
16305 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16306 {
16307   char buf[MSG_SIZ];
16308   int len = strlen(name);
16309   int val;
16310
16311   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16312     (*p) += len + 1;
16313     sscanf(*p, "%d", &val);
16314     *loc = (val != 0);
16315     while (**p && **p != ' ')
16316       (*p)++;
16317     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16318     SendToProgram(buf, cps);
16319     return TRUE;
16320   }
16321   return FALSE;
16322 }
16323
16324 int
16325 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16326 {
16327   char buf[MSG_SIZ];
16328   int len = strlen(name);
16329   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16330     (*p) += len + 1;
16331     sscanf(*p, "%d", loc);
16332     while (**p && **p != ' ') (*p)++;
16333     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16334     SendToProgram(buf, cps);
16335     return TRUE;
16336   }
16337   return FALSE;
16338 }
16339
16340 int
16341 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16342 {
16343   char buf[MSG_SIZ];
16344   int len = strlen(name);
16345   if (strncmp((*p), name, len) == 0
16346       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16347     (*p) += len + 2;
16348     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16349     sscanf(*p, "%[^\"]", *loc);
16350     while (**p && **p != '\"') (*p)++;
16351     if (**p == '\"') (*p)++;
16352     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16353     SendToProgram(buf, cps);
16354     return TRUE;
16355   }
16356   return FALSE;
16357 }
16358
16359 int
16360 ParseOption (Option *opt, ChessProgramState *cps)
16361 // [HGM] options: process the string that defines an engine option, and determine
16362 // name, type, default value, and allowed value range
16363 {
16364         char *p, *q, buf[MSG_SIZ];
16365         int n, min = (-1)<<31, max = 1<<31, def;
16366
16367         if(p = strstr(opt->name, " -spin ")) {
16368             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16369             if(max < min) max = min; // enforce consistency
16370             if(def < min) def = min;
16371             if(def > max) def = max;
16372             opt->value = def;
16373             opt->min = min;
16374             opt->max = max;
16375             opt->type = Spin;
16376         } else if((p = strstr(opt->name, " -slider "))) {
16377             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16378             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16379             if(max < min) max = min; // enforce consistency
16380             if(def < min) def = min;
16381             if(def > max) def = max;
16382             opt->value = def;
16383             opt->min = min;
16384             opt->max = max;
16385             opt->type = Spin; // Slider;
16386         } else if((p = strstr(opt->name, " -string "))) {
16387             opt->textValue = p+9;
16388             opt->type = TextBox;
16389         } else if((p = strstr(opt->name, " -file "))) {
16390             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16391             opt->textValue = p+7;
16392             opt->type = FileName; // FileName;
16393         } else if((p = strstr(opt->name, " -path "))) {
16394             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16395             opt->textValue = p+7;
16396             opt->type = PathName; // PathName;
16397         } else if(p = strstr(opt->name, " -check ")) {
16398             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16399             opt->value = (def != 0);
16400             opt->type = CheckBox;
16401         } else if(p = strstr(opt->name, " -combo ")) {
16402             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16403             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16404             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16405             opt->value = n = 0;
16406             while(q = StrStr(q, " /// ")) {
16407                 n++; *q = 0;    // count choices, and null-terminate each of them
16408                 q += 5;
16409                 if(*q == '*') { // remember default, which is marked with * prefix
16410                     q++;
16411                     opt->value = n;
16412                 }
16413                 cps->comboList[cps->comboCnt++] = q;
16414             }
16415             cps->comboList[cps->comboCnt++] = NULL;
16416             opt->max = n + 1;
16417             opt->type = ComboBox;
16418         } else if(p = strstr(opt->name, " -button")) {
16419             opt->type = Button;
16420         } else if(p = strstr(opt->name, " -save")) {
16421             opt->type = SaveButton;
16422         } else return FALSE;
16423         *p = 0; // terminate option name
16424         // now look if the command-line options define a setting for this engine option.
16425         if(cps->optionSettings && cps->optionSettings[0])
16426             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16427         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16428           snprintf(buf, MSG_SIZ, "option %s", p);
16429                 if(p = strstr(buf, ",")) *p = 0;
16430                 if(q = strchr(buf, '=')) switch(opt->type) {
16431                     case ComboBox:
16432                         for(n=0; n<opt->max; n++)
16433                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16434                         break;
16435                     case TextBox:
16436                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16437                         break;
16438                     case Spin:
16439                     case CheckBox:
16440                         opt->value = atoi(q+1);
16441                     default:
16442                         break;
16443                 }
16444                 strcat(buf, "\n");
16445                 SendToProgram(buf, cps);
16446         }
16447         return TRUE;
16448 }
16449
16450 void
16451 FeatureDone (ChessProgramState *cps, int val)
16452 {
16453   DelayedEventCallback cb = GetDelayedEvent();
16454   if ((cb == InitBackEnd3 && cps == &first) ||
16455       (cb == SettingsMenuIfReady && cps == &second) ||
16456       (cb == LoadEngine) ||
16457       (cb == TwoMachinesEventIfReady)) {
16458     CancelDelayedEvent();
16459     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16460   }
16461   cps->initDone = val;
16462   if(val) cps->reload = FALSE;
16463 }
16464
16465 /* Parse feature command from engine */
16466 void
16467 ParseFeatures (char *args, ChessProgramState *cps)
16468 {
16469   char *p = args;
16470   char *q = NULL;
16471   int val;
16472   char buf[MSG_SIZ];
16473
16474   for (;;) {
16475     while (*p == ' ') p++;
16476     if (*p == NULLCHAR) return;
16477
16478     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16479     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16480     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16481     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16482     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16483     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16484     if (BoolFeature(&p, "reuse", &val, cps)) {
16485       /* Engine can disable reuse, but can't enable it if user said no */
16486       if (!val) cps->reuse = FALSE;
16487       continue;
16488     }
16489     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16490     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16491       if (gameMode == TwoMachinesPlay) {
16492         DisplayTwoMachinesTitle();
16493       } else {
16494         DisplayTitle("");
16495       }
16496       continue;
16497     }
16498     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16499     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16500     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16501     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16502     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16503     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16504     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16505     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16506     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16507     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16508     if (IntFeature(&p, "done", &val, cps)) {
16509       FeatureDone(cps, val);
16510       continue;
16511     }
16512     /* Added by Tord: */
16513     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16514     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16515     /* End of additions by Tord */
16516
16517     /* [HGM] added features: */
16518     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16519     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16520     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16521     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16522     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16523     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16524     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16525     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16526         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16527         FREE(cps->option[cps->nrOptions].name);
16528         cps->option[cps->nrOptions].name = q; q = NULL;
16529         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16530           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16531             SendToProgram(buf, cps);
16532             continue;
16533         }
16534         if(cps->nrOptions >= MAX_OPTIONS) {
16535             cps->nrOptions--;
16536             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16537             DisplayError(buf, 0);
16538         }
16539         continue;
16540     }
16541     /* End of additions by HGM */
16542
16543     /* unknown feature: complain and skip */
16544     q = p;
16545     while (*q && *q != '=') q++;
16546     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16547     SendToProgram(buf, cps);
16548     p = q;
16549     if (*p == '=') {
16550       p++;
16551       if (*p == '\"') {
16552         p++;
16553         while (*p && *p != '\"') p++;
16554         if (*p == '\"') p++;
16555       } else {
16556         while (*p && *p != ' ') p++;
16557       }
16558     }
16559   }
16560
16561 }
16562
16563 void
16564 PeriodicUpdatesEvent (int newState)
16565 {
16566     if (newState == appData.periodicUpdates)
16567       return;
16568
16569     appData.periodicUpdates=newState;
16570
16571     /* Display type changes, so update it now */
16572 //    DisplayAnalysis();
16573
16574     /* Get the ball rolling again... */
16575     if (newState) {
16576         AnalysisPeriodicEvent(1);
16577         StartAnalysisClock();
16578     }
16579 }
16580
16581 void
16582 PonderNextMoveEvent (int newState)
16583 {
16584     if (newState == appData.ponderNextMove) return;
16585     if (gameMode == EditPosition) EditPositionDone(TRUE);
16586     if (newState) {
16587         SendToProgram("hard\n", &first);
16588         if (gameMode == TwoMachinesPlay) {
16589             SendToProgram("hard\n", &second);
16590         }
16591     } else {
16592         SendToProgram("easy\n", &first);
16593         thinkOutput[0] = NULLCHAR;
16594         if (gameMode == TwoMachinesPlay) {
16595             SendToProgram("easy\n", &second);
16596         }
16597     }
16598     appData.ponderNextMove = newState;
16599 }
16600
16601 void
16602 NewSettingEvent (int option, int *feature, char *command, int value)
16603 {
16604     char buf[MSG_SIZ];
16605
16606     if (gameMode == EditPosition) EditPositionDone(TRUE);
16607     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16608     if(feature == NULL || *feature) SendToProgram(buf, &first);
16609     if (gameMode == TwoMachinesPlay) {
16610         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16611     }
16612 }
16613
16614 void
16615 ShowThinkingEvent ()
16616 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16617 {
16618     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16619     int newState = appData.showThinking
16620         // [HGM] thinking: other features now need thinking output as well
16621         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16622
16623     if (oldState == newState) return;
16624     oldState = newState;
16625     if (gameMode == EditPosition) EditPositionDone(TRUE);
16626     if (oldState) {
16627         SendToProgram("post\n", &first);
16628         if (gameMode == TwoMachinesPlay) {
16629             SendToProgram("post\n", &second);
16630         }
16631     } else {
16632         SendToProgram("nopost\n", &first);
16633         thinkOutput[0] = NULLCHAR;
16634         if (gameMode == TwoMachinesPlay) {
16635             SendToProgram("nopost\n", &second);
16636         }
16637     }
16638 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16639 }
16640
16641 void
16642 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16643 {
16644   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16645   if (pr == NoProc) return;
16646   AskQuestion(title, question, replyPrefix, pr);
16647 }
16648
16649 void
16650 TypeInEvent (char firstChar)
16651 {
16652     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16653         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16654         gameMode == AnalyzeMode || gameMode == EditGame ||
16655         gameMode == EditPosition || gameMode == IcsExamining ||
16656         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16657         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16658                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16659                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16660         gameMode == Training) PopUpMoveDialog(firstChar);
16661 }
16662
16663 void
16664 TypeInDoneEvent (char *move)
16665 {
16666         Board board;
16667         int n, fromX, fromY, toX, toY;
16668         char promoChar;
16669         ChessMove moveType;
16670
16671         // [HGM] FENedit
16672         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16673                 EditPositionPasteFEN(move);
16674                 return;
16675         }
16676         // [HGM] movenum: allow move number to be typed in any mode
16677         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16678           ToNrEvent(2*n-1);
16679           return;
16680         }
16681         // undocumented kludge: allow command-line option to be typed in!
16682         // (potentially fatal, and does not implement the effect of the option.)
16683         // should only be used for options that are values on which future decisions will be made,
16684         // and definitely not on options that would be used during initialization.
16685         if(strstr(move, "!!! -") == move) {
16686             ParseArgsFromString(move+4);
16687             return;
16688         }
16689
16690       if (gameMode != EditGame && currentMove != forwardMostMove &&
16691         gameMode != Training) {
16692         DisplayMoveError(_("Displayed move is not current"));
16693       } else {
16694         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16695           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16696         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16697         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16698           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16699           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16700         } else {
16701           DisplayMoveError(_("Could not parse move"));
16702         }
16703       }
16704 }
16705
16706 void
16707 DisplayMove (int moveNumber)
16708 {
16709     char message[MSG_SIZ];
16710     char res[MSG_SIZ];
16711     char cpThinkOutput[MSG_SIZ];
16712
16713     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16714
16715     if (moveNumber == forwardMostMove - 1 ||
16716         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16717
16718         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16719
16720         if (strchr(cpThinkOutput, '\n')) {
16721             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16722         }
16723     } else {
16724         *cpThinkOutput = NULLCHAR;
16725     }
16726
16727     /* [AS] Hide thinking from human user */
16728     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16729         *cpThinkOutput = NULLCHAR;
16730         if( thinkOutput[0] != NULLCHAR ) {
16731             int i;
16732
16733             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16734                 cpThinkOutput[i] = '.';
16735             }
16736             cpThinkOutput[i] = NULLCHAR;
16737             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16738         }
16739     }
16740
16741     if (moveNumber == forwardMostMove - 1 &&
16742         gameInfo.resultDetails != NULL) {
16743         if (gameInfo.resultDetails[0] == NULLCHAR) {
16744           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16745         } else {
16746           snprintf(res, MSG_SIZ, " {%s} %s",
16747                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16748         }
16749     } else {
16750         res[0] = NULLCHAR;
16751     }
16752
16753     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16754         DisplayMessage(res, cpThinkOutput);
16755     } else {
16756       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16757                 WhiteOnMove(moveNumber) ? " " : ".. ",
16758                 parseList[moveNumber], res);
16759         DisplayMessage(message, cpThinkOutput);
16760     }
16761 }
16762
16763 void
16764 DisplayComment (int moveNumber, char *text)
16765 {
16766     char title[MSG_SIZ];
16767
16768     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16769       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16770     } else {
16771       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16772               WhiteOnMove(moveNumber) ? " " : ".. ",
16773               parseList[moveNumber]);
16774     }
16775     if (text != NULL && (appData.autoDisplayComment || commentUp))
16776         CommentPopUp(title, text);
16777 }
16778
16779 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16780  * might be busy thinking or pondering.  It can be omitted if your
16781  * gnuchess is configured to stop thinking immediately on any user
16782  * input.  However, that gnuchess feature depends on the FIONREAD
16783  * ioctl, which does not work properly on some flavors of Unix.
16784  */
16785 void
16786 Attention (ChessProgramState *cps)
16787 {
16788 #if ATTENTION
16789     if (!cps->useSigint) return;
16790     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16791     switch (gameMode) {
16792       case MachinePlaysWhite:
16793       case MachinePlaysBlack:
16794       case TwoMachinesPlay:
16795       case IcsPlayingWhite:
16796       case IcsPlayingBlack:
16797       case AnalyzeMode:
16798       case AnalyzeFile:
16799         /* Skip if we know it isn't thinking */
16800         if (!cps->maybeThinking) return;
16801         if (appData.debugMode)
16802           fprintf(debugFP, "Interrupting %s\n", cps->which);
16803         InterruptChildProcess(cps->pr);
16804         cps->maybeThinking = FALSE;
16805         break;
16806       default:
16807         break;
16808     }
16809 #endif /*ATTENTION*/
16810 }
16811
16812 int
16813 CheckFlags ()
16814 {
16815     if (whiteTimeRemaining <= 0) {
16816         if (!whiteFlag) {
16817             whiteFlag = TRUE;
16818             if (appData.icsActive) {
16819                 if (appData.autoCallFlag &&
16820                     gameMode == IcsPlayingBlack && !blackFlag) {
16821                   SendToICS(ics_prefix);
16822                   SendToICS("flag\n");
16823                 }
16824             } else {
16825                 if (blackFlag) {
16826                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16827                 } else {
16828                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16829                     if (appData.autoCallFlag) {
16830                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16831                         return TRUE;
16832                     }
16833                 }
16834             }
16835         }
16836     }
16837     if (blackTimeRemaining <= 0) {
16838         if (!blackFlag) {
16839             blackFlag = TRUE;
16840             if (appData.icsActive) {
16841                 if (appData.autoCallFlag &&
16842                     gameMode == IcsPlayingWhite && !whiteFlag) {
16843                   SendToICS(ics_prefix);
16844                   SendToICS("flag\n");
16845                 }
16846             } else {
16847                 if (whiteFlag) {
16848                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16849                 } else {
16850                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16851                     if (appData.autoCallFlag) {
16852                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16853                         return TRUE;
16854                     }
16855                 }
16856             }
16857         }
16858     }
16859     return FALSE;
16860 }
16861
16862 void
16863 CheckTimeControl ()
16864 {
16865     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16866         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16867
16868     /*
16869      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16870      */
16871     if ( !WhiteOnMove(forwardMostMove) ) {
16872         /* White made time control */
16873         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16874         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16875         /* [HGM] time odds: correct new time quota for time odds! */
16876                                             / WhitePlayer()->timeOdds;
16877         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16878     } else {
16879         lastBlack -= blackTimeRemaining;
16880         /* Black made time control */
16881         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16882                                             / WhitePlayer()->other->timeOdds;
16883         lastWhite = whiteTimeRemaining;
16884     }
16885 }
16886
16887 void
16888 DisplayBothClocks ()
16889 {
16890     int wom = gameMode == EditPosition ?
16891       !blackPlaysFirst : WhiteOnMove(currentMove);
16892     DisplayWhiteClock(whiteTimeRemaining, wom);
16893     DisplayBlackClock(blackTimeRemaining, !wom);
16894 }
16895
16896
16897 /* Timekeeping seems to be a portability nightmare.  I think everyone
16898    has ftime(), but I'm really not sure, so I'm including some ifdefs
16899    to use other calls if you don't.  Clocks will be less accurate if
16900    you have neither ftime nor gettimeofday.
16901 */
16902
16903 /* VS 2008 requires the #include outside of the function */
16904 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16905 #include <sys/timeb.h>
16906 #endif
16907
16908 /* Get the current time as a TimeMark */
16909 void
16910 GetTimeMark (TimeMark *tm)
16911 {
16912 #if HAVE_GETTIMEOFDAY
16913
16914     struct timeval timeVal;
16915     struct timezone timeZone;
16916
16917     gettimeofday(&timeVal, &timeZone);
16918     tm->sec = (long) timeVal.tv_sec;
16919     tm->ms = (int) (timeVal.tv_usec / 1000L);
16920
16921 #else /*!HAVE_GETTIMEOFDAY*/
16922 #if HAVE_FTIME
16923
16924 // include <sys/timeb.h> / moved to just above start of function
16925     struct timeb timeB;
16926
16927     ftime(&timeB);
16928     tm->sec = (long) timeB.time;
16929     tm->ms = (int) timeB.millitm;
16930
16931 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16932     tm->sec = (long) time(NULL);
16933     tm->ms = 0;
16934 #endif
16935 #endif
16936 }
16937
16938 /* Return the difference in milliseconds between two
16939    time marks.  We assume the difference will fit in a long!
16940 */
16941 long
16942 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16943 {
16944     return 1000L*(tm2->sec - tm1->sec) +
16945            (long) (tm2->ms - tm1->ms);
16946 }
16947
16948
16949 /*
16950  * Code to manage the game clocks.
16951  *
16952  * In tournament play, black starts the clock and then white makes a move.
16953  * We give the human user a slight advantage if he is playing white---the
16954  * clocks don't run until he makes his first move, so it takes zero time.
16955  * Also, we don't account for network lag, so we could get out of sync
16956  * with GNU Chess's clock -- but then, referees are always right.
16957  */
16958
16959 static TimeMark tickStartTM;
16960 static long intendedTickLength;
16961
16962 long
16963 NextTickLength (long timeRemaining)
16964 {
16965     long nominalTickLength, nextTickLength;
16966
16967     if (timeRemaining > 0L && timeRemaining <= 10000L)
16968       nominalTickLength = 100L;
16969     else
16970       nominalTickLength = 1000L;
16971     nextTickLength = timeRemaining % nominalTickLength;
16972     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16973
16974     return nextTickLength;
16975 }
16976
16977 /* Adjust clock one minute up or down */
16978 void
16979 AdjustClock (Boolean which, int dir)
16980 {
16981     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16982     if(which) blackTimeRemaining += 60000*dir;
16983     else      whiteTimeRemaining += 60000*dir;
16984     DisplayBothClocks();
16985     adjustedClock = TRUE;
16986 }
16987
16988 /* Stop clocks and reset to a fresh time control */
16989 void
16990 ResetClocks ()
16991 {
16992     (void) StopClockTimer();
16993     if (appData.icsActive) {
16994         whiteTimeRemaining = blackTimeRemaining = 0;
16995     } else if (searchTime) {
16996         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16997         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16998     } else { /* [HGM] correct new time quote for time odds */
16999         whiteTC = blackTC = fullTimeControlString;
17000         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17001         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17002     }
17003     if (whiteFlag || blackFlag) {
17004         DisplayTitle("");
17005         whiteFlag = blackFlag = FALSE;
17006     }
17007     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17008     DisplayBothClocks();
17009     adjustedClock = FALSE;
17010 }
17011
17012 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17013
17014 /* Decrement running clock by amount of time that has passed */
17015 void
17016 DecrementClocks ()
17017 {
17018     long timeRemaining;
17019     long lastTickLength, fudge;
17020     TimeMark now;
17021
17022     if (!appData.clockMode) return;
17023     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17024
17025     GetTimeMark(&now);
17026
17027     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17028
17029     /* Fudge if we woke up a little too soon */
17030     fudge = intendedTickLength - lastTickLength;
17031     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17032
17033     if (WhiteOnMove(forwardMostMove)) {
17034         if(whiteNPS >= 0) lastTickLength = 0;
17035         timeRemaining = whiteTimeRemaining -= lastTickLength;
17036         if(timeRemaining < 0 && !appData.icsActive) {
17037             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17038             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17039                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17040                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17041             }
17042         }
17043         DisplayWhiteClock(whiteTimeRemaining - fudge,
17044                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17045     } else {
17046         if(blackNPS >= 0) lastTickLength = 0;
17047         timeRemaining = blackTimeRemaining -= lastTickLength;
17048         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17049             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17050             if(suddenDeath) {
17051                 blackStartMove = forwardMostMove;
17052                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17053             }
17054         }
17055         DisplayBlackClock(blackTimeRemaining - fudge,
17056                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17057     }
17058     if (CheckFlags()) return;
17059
17060     if(twoBoards) { // count down secondary board's clocks as well
17061         activePartnerTime -= lastTickLength;
17062         partnerUp = 1;
17063         if(activePartner == 'W')
17064             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17065         else
17066             DisplayBlackClock(activePartnerTime, TRUE);
17067         partnerUp = 0;
17068     }
17069
17070     tickStartTM = now;
17071     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17072     StartClockTimer(intendedTickLength);
17073
17074     /* if the time remaining has fallen below the alarm threshold, sound the
17075      * alarm. if the alarm has sounded and (due to a takeback or time control
17076      * with increment) the time remaining has increased to a level above the
17077      * threshold, reset the alarm so it can sound again.
17078      */
17079
17080     if (appData.icsActive && appData.icsAlarm) {
17081
17082         /* make sure we are dealing with the user's clock */
17083         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17084                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17085            )) return;
17086
17087         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17088             alarmSounded = FALSE;
17089         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17090             PlayAlarmSound();
17091             alarmSounded = TRUE;
17092         }
17093     }
17094 }
17095
17096
17097 /* A player has just moved, so stop the previously running
17098    clock and (if in clock mode) start the other one.
17099    We redisplay both clocks in case we're in ICS mode, because
17100    ICS gives us an update to both clocks after every move.
17101    Note that this routine is called *after* forwardMostMove
17102    is updated, so the last fractional tick must be subtracted
17103    from the color that is *not* on move now.
17104 */
17105 void
17106 SwitchClocks (int newMoveNr)
17107 {
17108     long lastTickLength;
17109     TimeMark now;
17110     int flagged = FALSE;
17111
17112     GetTimeMark(&now);
17113
17114     if (StopClockTimer() && appData.clockMode) {
17115         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17116         if (!WhiteOnMove(forwardMostMove)) {
17117             if(blackNPS >= 0) lastTickLength = 0;
17118             blackTimeRemaining -= lastTickLength;
17119            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17120 //         if(pvInfoList[forwardMostMove].time == -1)
17121                  pvInfoList[forwardMostMove].time =               // use GUI time
17122                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17123         } else {
17124            if(whiteNPS >= 0) lastTickLength = 0;
17125            whiteTimeRemaining -= lastTickLength;
17126            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17127 //         if(pvInfoList[forwardMostMove].time == -1)
17128                  pvInfoList[forwardMostMove].time =
17129                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17130         }
17131         flagged = CheckFlags();
17132     }
17133     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17134     CheckTimeControl();
17135
17136     if (flagged || !appData.clockMode) return;
17137
17138     switch (gameMode) {
17139       case MachinePlaysBlack:
17140       case MachinePlaysWhite:
17141       case BeginningOfGame:
17142         if (pausing) return;
17143         break;
17144
17145       case EditGame:
17146       case PlayFromGameFile:
17147       case IcsExamining:
17148         return;
17149
17150       default:
17151         break;
17152     }
17153
17154     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17155         if(WhiteOnMove(forwardMostMove))
17156              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17157         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17158     }
17159
17160     tickStartTM = now;
17161     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17162       whiteTimeRemaining : blackTimeRemaining);
17163     StartClockTimer(intendedTickLength);
17164 }
17165
17166
17167 /* Stop both clocks */
17168 void
17169 StopClocks ()
17170 {
17171     long lastTickLength;
17172     TimeMark now;
17173
17174     if (!StopClockTimer()) return;
17175     if (!appData.clockMode) return;
17176
17177     GetTimeMark(&now);
17178
17179     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17180     if (WhiteOnMove(forwardMostMove)) {
17181         if(whiteNPS >= 0) lastTickLength = 0;
17182         whiteTimeRemaining -= lastTickLength;
17183         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17184     } else {
17185         if(blackNPS >= 0) lastTickLength = 0;
17186         blackTimeRemaining -= lastTickLength;
17187         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17188     }
17189     CheckFlags();
17190 }
17191
17192 /* Start clock of player on move.  Time may have been reset, so
17193    if clock is already running, stop and restart it. */
17194 void
17195 StartClocks ()
17196 {
17197     (void) StopClockTimer(); /* in case it was running already */
17198     DisplayBothClocks();
17199     if (CheckFlags()) return;
17200
17201     if (!appData.clockMode) return;
17202     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17203
17204     GetTimeMark(&tickStartTM);
17205     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17206       whiteTimeRemaining : blackTimeRemaining);
17207
17208    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17209     whiteNPS = blackNPS = -1;
17210     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17211        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17212         whiteNPS = first.nps;
17213     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17214        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17215         blackNPS = first.nps;
17216     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17217         whiteNPS = second.nps;
17218     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17219         blackNPS = second.nps;
17220     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17221
17222     StartClockTimer(intendedTickLength);
17223 }
17224
17225 char *
17226 TimeString (long ms)
17227 {
17228     long second, minute, hour, day;
17229     char *sign = "";
17230     static char buf[32];
17231
17232     if (ms > 0 && ms <= 9900) {
17233       /* convert milliseconds to tenths, rounding up */
17234       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17235
17236       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17237       return buf;
17238     }
17239
17240     /* convert milliseconds to seconds, rounding up */
17241     /* use floating point to avoid strangeness of integer division
17242        with negative dividends on many machines */
17243     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17244
17245     if (second < 0) {
17246         sign = "-";
17247         second = -second;
17248     }
17249
17250     day = second / (60 * 60 * 24);
17251     second = second % (60 * 60 * 24);
17252     hour = second / (60 * 60);
17253     second = second % (60 * 60);
17254     minute = second / 60;
17255     second = second % 60;
17256
17257     if (day > 0)
17258       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17259               sign, day, hour, minute, second);
17260     else if (hour > 0)
17261       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17262     else
17263       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17264
17265     return buf;
17266 }
17267
17268
17269 /*
17270  * This is necessary because some C libraries aren't ANSI C compliant yet.
17271  */
17272 char *
17273 StrStr (char *string, char *match)
17274 {
17275     int i, length;
17276
17277     length = strlen(match);
17278
17279     for (i = strlen(string) - length; i >= 0; i--, string++)
17280       if (!strncmp(match, string, length))
17281         return string;
17282
17283     return NULL;
17284 }
17285
17286 char *
17287 StrCaseStr (char *string, char *match)
17288 {
17289     int i, j, length;
17290
17291     length = strlen(match);
17292
17293     for (i = strlen(string) - length; i >= 0; i--, string++) {
17294         for (j = 0; j < length; j++) {
17295             if (ToLower(match[j]) != ToLower(string[j]))
17296               break;
17297         }
17298         if (j == length) return string;
17299     }
17300
17301     return NULL;
17302 }
17303
17304 #ifndef _amigados
17305 int
17306 StrCaseCmp (char *s1, char *s2)
17307 {
17308     char c1, c2;
17309
17310     for (;;) {
17311         c1 = ToLower(*s1++);
17312         c2 = ToLower(*s2++);
17313         if (c1 > c2) return 1;
17314         if (c1 < c2) return -1;
17315         if (c1 == NULLCHAR) return 0;
17316     }
17317 }
17318
17319
17320 int
17321 ToLower (int c)
17322 {
17323     return isupper(c) ? tolower(c) : c;
17324 }
17325
17326
17327 int
17328 ToUpper (int c)
17329 {
17330     return islower(c) ? toupper(c) : c;
17331 }
17332 #endif /* !_amigados    */
17333
17334 char *
17335 StrSave (char *s)
17336 {
17337   char *ret;
17338
17339   if ((ret = (char *) malloc(strlen(s) + 1)))
17340     {
17341       safeStrCpy(ret, s, strlen(s)+1);
17342     }
17343   return ret;
17344 }
17345
17346 char *
17347 StrSavePtr (char *s, char **savePtr)
17348 {
17349     if (*savePtr) {
17350         free(*savePtr);
17351     }
17352     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17353       safeStrCpy(*savePtr, s, strlen(s)+1);
17354     }
17355     return(*savePtr);
17356 }
17357
17358 char *
17359 PGNDate ()
17360 {
17361     time_t clock;
17362     struct tm *tm;
17363     char buf[MSG_SIZ];
17364
17365     clock = time((time_t *)NULL);
17366     tm = localtime(&clock);
17367     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17368             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17369     return StrSave(buf);
17370 }
17371
17372
17373 char *
17374 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17375 {
17376     int i, j, fromX, fromY, toX, toY;
17377     int whiteToPlay;
17378     char buf[MSG_SIZ];
17379     char *p, *q;
17380     int emptycount;
17381     ChessSquare piece;
17382
17383     whiteToPlay = (gameMode == EditPosition) ?
17384       !blackPlaysFirst : (move % 2 == 0);
17385     p = buf;
17386
17387     /* Piece placement data */
17388     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17389         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17390         emptycount = 0;
17391         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17392             if (boards[move][i][j] == EmptySquare) {
17393                 emptycount++;
17394             } else { ChessSquare piece = boards[move][i][j];
17395                 if (emptycount > 0) {
17396                     if(emptycount<10) /* [HGM] can be >= 10 */
17397                         *p++ = '0' + emptycount;
17398                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17399                     emptycount = 0;
17400                 }
17401                 if(PieceToChar(piece) == '+') {
17402                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17403                     *p++ = '+';
17404                     piece = (ChessSquare)(DEMOTED piece);
17405                 }
17406                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17407                 if(p[-1] == '~') {
17408                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17409                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17410                     *p++ = '~';
17411                 }
17412             }
17413         }
17414         if (emptycount > 0) {
17415             if(emptycount<10) /* [HGM] can be >= 10 */
17416                 *p++ = '0' + emptycount;
17417             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17418             emptycount = 0;
17419         }
17420         *p++ = '/';
17421     }
17422     *(p - 1) = ' ';
17423
17424     /* [HGM] print Crazyhouse or Shogi holdings */
17425     if( gameInfo.holdingsWidth ) {
17426         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17427         q = p;
17428         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17429             piece = boards[move][i][BOARD_WIDTH-1];
17430             if( piece != EmptySquare )
17431               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17432                   *p++ = PieceToChar(piece);
17433         }
17434         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17435             piece = boards[move][BOARD_HEIGHT-i-1][0];
17436             if( piece != EmptySquare )
17437               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17438                   *p++ = PieceToChar(piece);
17439         }
17440
17441         if( q == p ) *p++ = '-';
17442         *p++ = ']';
17443         *p++ = ' ';
17444     }
17445
17446     /* Active color */
17447     *p++ = whiteToPlay ? 'w' : 'b';
17448     *p++ = ' ';
17449
17450   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17451     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17452   } else {
17453   if(nrCastlingRights) {
17454      q = p;
17455      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17456        /* [HGM] write directly from rights */
17457            if(boards[move][CASTLING][2] != NoRights &&
17458               boards[move][CASTLING][0] != NoRights   )
17459                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17460            if(boards[move][CASTLING][2] != NoRights &&
17461               boards[move][CASTLING][1] != NoRights   )
17462                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17463            if(boards[move][CASTLING][5] != NoRights &&
17464               boards[move][CASTLING][3] != NoRights   )
17465                 *p++ = boards[move][CASTLING][3] + AAA;
17466            if(boards[move][CASTLING][5] != NoRights &&
17467               boards[move][CASTLING][4] != NoRights   )
17468                 *p++ = boards[move][CASTLING][4] + AAA;
17469      } else {
17470
17471         /* [HGM] write true castling rights */
17472         if( nrCastlingRights == 6 ) {
17473             int q, k=0;
17474             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17475                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17476             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17477                  boards[move][CASTLING][2] != NoRights  );
17478             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17479                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17480                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17481                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17482                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17483             }
17484             if(q) *p++ = 'Q';
17485             k = 0;
17486             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17487                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17488             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17489                  boards[move][CASTLING][5] != NoRights  );
17490             if(gameInfo.variant == VariantSChess) {
17491                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17492                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17493                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17494                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17495             }
17496             if(q) *p++ = 'q';
17497         }
17498      }
17499      if (q == p) *p++ = '-'; /* No castling rights */
17500      *p++ = ' ';
17501   }
17502
17503   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17504      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17505      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17506     /* En passant target square */
17507     if (move > backwardMostMove) {
17508         fromX = moveList[move - 1][0] - AAA;
17509         fromY = moveList[move - 1][1] - ONE;
17510         toX = moveList[move - 1][2] - AAA;
17511         toY = moveList[move - 1][3] - ONE;
17512         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17513             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17514             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17515             fromX == toX) {
17516             /* 2-square pawn move just happened */
17517             *p++ = toX + AAA;
17518             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17519         } else {
17520             *p++ = '-';
17521         }
17522     } else if(move == backwardMostMove) {
17523         // [HGM] perhaps we should always do it like this, and forget the above?
17524         if((signed char)boards[move][EP_STATUS] >= 0) {
17525             *p++ = boards[move][EP_STATUS] + AAA;
17526             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17527         } else {
17528             *p++ = '-';
17529         }
17530     } else {
17531         *p++ = '-';
17532     }
17533     *p++ = ' ';
17534   }
17535   }
17536
17537     if(moveCounts)
17538     {   int i = 0, j=move;
17539
17540         /* [HGM] find reversible plies */
17541         if (appData.debugMode) { int k;
17542             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17543             for(k=backwardMostMove; k<=forwardMostMove; k++)
17544                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17545
17546         }
17547
17548         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17549         if( j == backwardMostMove ) i += initialRulePlies;
17550         sprintf(p, "%d ", i);
17551         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17552
17553         /* Fullmove number */
17554         sprintf(p, "%d", (move / 2) + 1);
17555     } else *--p = NULLCHAR;
17556
17557     return StrSave(buf);
17558 }
17559
17560 Boolean
17561 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17562 {
17563     int i, j, k, w=0;
17564     char *p, c;
17565     int emptycount, virgin[BOARD_FILES];
17566     ChessSquare piece;
17567
17568     p = fen;
17569
17570     /* Piece placement data */
17571     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17572         j = 0;
17573         for (;;) {
17574             if (*p == '/' || *p == ' ' || *p == '[' ) {
17575                 if(j > w) w = j;
17576                 emptycount = gameInfo.boardWidth - j;
17577                 while (emptycount--)
17578                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17579                 if (*p == '/') p++;
17580                 else if(autoSize) { // we stumbled unexpectedly into end of board
17581                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17582                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17583                     }
17584                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17585                 }
17586                 break;
17587 #if(BOARD_FILES >= 10)
17588             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17589                 p++; emptycount=10;
17590                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17591                 while (emptycount--)
17592                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17593 #endif
17594             } else if (*p == '*') {
17595                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17596             } else if (isdigit(*p)) {
17597                 emptycount = *p++ - '0';
17598                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17599                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17600                 while (emptycount--)
17601                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17602             } else if (*p == '+' || isalpha(*p)) {
17603                 if (j >= gameInfo.boardWidth) return FALSE;
17604                 if(*p=='+') {
17605                     piece = CharToPiece(*++p);
17606                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17607                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17608                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17609                 } else piece = CharToPiece(*p++);
17610
17611                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17612                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17613                     piece = (ChessSquare) (PROMOTED piece);
17614                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17615                     p++;
17616                 }
17617                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17618             } else {
17619                 return FALSE;
17620             }
17621         }
17622     }
17623     while (*p == '/' || *p == ' ') p++;
17624
17625     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17626
17627     /* [HGM] by default clear Crazyhouse holdings, if present */
17628     if(gameInfo.holdingsWidth) {
17629        for(i=0; i<BOARD_HEIGHT; i++) {
17630            board[i][0]             = EmptySquare; /* black holdings */
17631            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17632            board[i][1]             = (ChessSquare) 0; /* black counts */
17633            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17634        }
17635     }
17636
17637     /* [HGM] look for Crazyhouse holdings here */
17638     while(*p==' ') p++;
17639     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17640         if(*p == '[') p++;
17641         if(*p == '-' ) p++; /* empty holdings */ else {
17642             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17643             /* if we would allow FEN reading to set board size, we would   */
17644             /* have to add holdings and shift the board read so far here   */
17645             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17646                 p++;
17647                 if((int) piece >= (int) BlackPawn ) {
17648                     i = (int)piece - (int)BlackPawn;
17649                     i = PieceToNumber((ChessSquare)i);
17650                     if( i >= gameInfo.holdingsSize ) return FALSE;
17651                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17652                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17653                 } else {
17654                     i = (int)piece - (int)WhitePawn;
17655                     i = PieceToNumber((ChessSquare)i);
17656                     if( i >= gameInfo.holdingsSize ) return FALSE;
17657                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17658                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17659                 }
17660             }
17661         }
17662         if(*p == ']') p++;
17663     }
17664
17665     while(*p == ' ') p++;
17666
17667     /* Active color */
17668     c = *p++;
17669     if(appData.colorNickNames) {
17670       if( c == appData.colorNickNames[0] ) c = 'w'; else
17671       if( c == appData.colorNickNames[1] ) c = 'b';
17672     }
17673     switch (c) {
17674       case 'w':
17675         *blackPlaysFirst = FALSE;
17676         break;
17677       case 'b':
17678         *blackPlaysFirst = TRUE;
17679         break;
17680       default:
17681         return FALSE;
17682     }
17683
17684     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17685     /* return the extra info in global variiables             */
17686
17687     /* set defaults in case FEN is incomplete */
17688     board[EP_STATUS] = EP_UNKNOWN;
17689     for(i=0; i<nrCastlingRights; i++ ) {
17690         board[CASTLING][i] =
17691             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17692     }   /* assume possible unless obviously impossible */
17693     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17694     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17695     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17696                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17697     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17698     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17699     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17700                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17701     FENrulePlies = 0;
17702
17703     while(*p==' ') p++;
17704     if(nrCastlingRights) {
17705       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17706       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17707           /* castling indicator present, so default becomes no castlings */
17708           for(i=0; i<nrCastlingRights; i++ ) {
17709                  board[CASTLING][i] = NoRights;
17710           }
17711       }
17712       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17713              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17714              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17715              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17716         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17717
17718         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17719             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17720             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17721         }
17722         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17723             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17724         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17725                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17726         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17727                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17728         switch(c) {
17729           case'K':
17730               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17731               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17732               board[CASTLING][2] = whiteKingFile;
17733               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17734               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17735               break;
17736           case'Q':
17737               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17738               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17739               board[CASTLING][2] = whiteKingFile;
17740               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17741               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17742               break;
17743           case'k':
17744               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17745               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17746               board[CASTLING][5] = blackKingFile;
17747               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17748               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17749               break;
17750           case'q':
17751               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17752               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17753               board[CASTLING][5] = blackKingFile;
17754               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17755               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17756           case '-':
17757               break;
17758           default: /* FRC castlings */
17759               if(c >= 'a') { /* black rights */
17760                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17761                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17762                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17763                   if(i == BOARD_RGHT) break;
17764                   board[CASTLING][5] = i;
17765                   c -= AAA;
17766                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17767                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17768                   if(c > i)
17769                       board[CASTLING][3] = c;
17770                   else
17771                       board[CASTLING][4] = c;
17772               } else { /* white rights */
17773                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17774                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17775                     if(board[0][i] == WhiteKing) break;
17776                   if(i == BOARD_RGHT) break;
17777                   board[CASTLING][2] = i;
17778                   c -= AAA - 'a' + 'A';
17779                   if(board[0][c] >= WhiteKing) break;
17780                   if(c > i)
17781                       board[CASTLING][0] = c;
17782                   else
17783                       board[CASTLING][1] = c;
17784               }
17785         }
17786       }
17787       for(i=0; i<nrCastlingRights; i++)
17788         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17789       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17790     if (appData.debugMode) {
17791         fprintf(debugFP, "FEN castling rights:");
17792         for(i=0; i<nrCastlingRights; i++)
17793         fprintf(debugFP, " %d", board[CASTLING][i]);
17794         fprintf(debugFP, "\n");
17795     }
17796
17797       while(*p==' ') p++;
17798     }
17799
17800     /* read e.p. field in games that know e.p. capture */
17801     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17802        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17803        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17804       if(*p=='-') {
17805         p++; board[EP_STATUS] = EP_NONE;
17806       } else {
17807          char c = *p++ - AAA;
17808
17809          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17810          if(*p >= '0' && *p <='9') p++;
17811          board[EP_STATUS] = c;
17812       }
17813     }
17814
17815
17816     if(sscanf(p, "%d", &i) == 1) {
17817         FENrulePlies = i; /* 50-move ply counter */
17818         /* (The move number is still ignored)    */
17819     }
17820
17821     return TRUE;
17822 }
17823
17824 void
17825 EditPositionPasteFEN (char *fen)
17826 {
17827   if (fen != NULL) {
17828     Board initial_position;
17829
17830     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17831       DisplayError(_("Bad FEN position in clipboard"), 0);
17832       return ;
17833     } else {
17834       int savedBlackPlaysFirst = blackPlaysFirst;
17835       EditPositionEvent();
17836       blackPlaysFirst = savedBlackPlaysFirst;
17837       CopyBoard(boards[0], initial_position);
17838       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17839       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17840       DisplayBothClocks();
17841       DrawPosition(FALSE, boards[currentMove]);
17842     }
17843   }
17844 }
17845
17846 static char cseq[12] = "\\   ";
17847
17848 Boolean
17849 set_cont_sequence (char *new_seq)
17850 {
17851     int len;
17852     Boolean ret;
17853
17854     // handle bad attempts to set the sequence
17855         if (!new_seq)
17856                 return 0; // acceptable error - no debug
17857
17858     len = strlen(new_seq);
17859     ret = (len > 0) && (len < sizeof(cseq));
17860     if (ret)
17861       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17862     else if (appData.debugMode)
17863       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17864     return ret;
17865 }
17866
17867 /*
17868     reformat a source message so words don't cross the width boundary.  internal
17869     newlines are not removed.  returns the wrapped size (no null character unless
17870     included in source message).  If dest is NULL, only calculate the size required
17871     for the dest buffer.  lp argument indicats line position upon entry, and it's
17872     passed back upon exit.
17873 */
17874 int
17875 wrap (char *dest, char *src, int count, int width, int *lp)
17876 {
17877     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17878
17879     cseq_len = strlen(cseq);
17880     old_line = line = *lp;
17881     ansi = len = clen = 0;
17882
17883     for (i=0; i < count; i++)
17884     {
17885         if (src[i] == '\033')
17886             ansi = 1;
17887
17888         // if we hit the width, back up
17889         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17890         {
17891             // store i & len in case the word is too long
17892             old_i = i, old_len = len;
17893
17894             // find the end of the last word
17895             while (i && src[i] != ' ' && src[i] != '\n')
17896             {
17897                 i--;
17898                 len--;
17899             }
17900
17901             // word too long?  restore i & len before splitting it
17902             if ((old_i-i+clen) >= width)
17903             {
17904                 i = old_i;
17905                 len = old_len;
17906             }
17907
17908             // extra space?
17909             if (i && src[i-1] == ' ')
17910                 len--;
17911
17912             if (src[i] != ' ' && src[i] != '\n')
17913             {
17914                 i--;
17915                 if (len)
17916                     len--;
17917             }
17918
17919             // now append the newline and continuation sequence
17920             if (dest)
17921                 dest[len] = '\n';
17922             len++;
17923             if (dest)
17924                 strncpy(dest+len, cseq, cseq_len);
17925             len += cseq_len;
17926             line = cseq_len;
17927             clen = cseq_len;
17928             continue;
17929         }
17930
17931         if (dest)
17932             dest[len] = src[i];
17933         len++;
17934         if (!ansi)
17935             line++;
17936         if (src[i] == '\n')
17937             line = 0;
17938         if (src[i] == 'm')
17939             ansi = 0;
17940     }
17941     if (dest && appData.debugMode)
17942     {
17943         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17944             count, width, line, len, *lp);
17945         show_bytes(debugFP, src, count);
17946         fprintf(debugFP, "\ndest: ");
17947         show_bytes(debugFP, dest, len);
17948         fprintf(debugFP, "\n");
17949     }
17950     *lp = dest ? line : old_line;
17951
17952     return len;
17953 }
17954
17955 // [HGM] vari: routines for shelving variations
17956 Boolean modeRestore = FALSE;
17957
17958 void
17959 PushInner (int firstMove, int lastMove)
17960 {
17961         int i, j, nrMoves = lastMove - firstMove;
17962
17963         // push current tail of game on stack
17964         savedResult[storedGames] = gameInfo.result;
17965         savedDetails[storedGames] = gameInfo.resultDetails;
17966         gameInfo.resultDetails = NULL;
17967         savedFirst[storedGames] = firstMove;
17968         savedLast [storedGames] = lastMove;
17969         savedFramePtr[storedGames] = framePtr;
17970         framePtr -= nrMoves; // reserve space for the boards
17971         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17972             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17973             for(j=0; j<MOVE_LEN; j++)
17974                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17975             for(j=0; j<2*MOVE_LEN; j++)
17976                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17977             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17978             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17979             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17980             pvInfoList[firstMove+i-1].depth = 0;
17981             commentList[framePtr+i] = commentList[firstMove+i];
17982             commentList[firstMove+i] = NULL;
17983         }
17984
17985         storedGames++;
17986         forwardMostMove = firstMove; // truncate game so we can start variation
17987 }
17988
17989 void
17990 PushTail (int firstMove, int lastMove)
17991 {
17992         if(appData.icsActive) { // only in local mode
17993                 forwardMostMove = currentMove; // mimic old ICS behavior
17994                 return;
17995         }
17996         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17997
17998         PushInner(firstMove, lastMove);
17999         if(storedGames == 1) GreyRevert(FALSE);
18000         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18001 }
18002
18003 void
18004 PopInner (Boolean annotate)
18005 {
18006         int i, j, nrMoves;
18007         char buf[8000], moveBuf[20];
18008
18009         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18010         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18011         nrMoves = savedLast[storedGames] - currentMove;
18012         if(annotate) {
18013                 int cnt = 10;
18014                 if(!WhiteOnMove(currentMove))
18015                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18016                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18017                 for(i=currentMove; i<forwardMostMove; i++) {
18018                         if(WhiteOnMove(i))
18019                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18020                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18021                         strcat(buf, moveBuf);
18022                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18023                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18024                 }
18025                 strcat(buf, ")");
18026         }
18027         for(i=1; i<=nrMoves; i++) { // copy last variation back
18028             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18029             for(j=0; j<MOVE_LEN; j++)
18030                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18031             for(j=0; j<2*MOVE_LEN; j++)
18032                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18033             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18034             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18035             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18036             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18037             commentList[currentMove+i] = commentList[framePtr+i];
18038             commentList[framePtr+i] = NULL;
18039         }
18040         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18041         framePtr = savedFramePtr[storedGames];
18042         gameInfo.result = savedResult[storedGames];
18043         if(gameInfo.resultDetails != NULL) {
18044             free(gameInfo.resultDetails);
18045       }
18046         gameInfo.resultDetails = savedDetails[storedGames];
18047         forwardMostMove = currentMove + nrMoves;
18048 }
18049
18050 Boolean
18051 PopTail (Boolean annotate)
18052 {
18053         if(appData.icsActive) return FALSE; // only in local mode
18054         if(!storedGames) return FALSE; // sanity
18055         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18056
18057         PopInner(annotate);
18058         if(currentMove < forwardMostMove) ForwardEvent(); else
18059         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18060
18061         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18062         return TRUE;
18063 }
18064
18065 void
18066 CleanupTail ()
18067 {       // remove all shelved variations
18068         int i;
18069         for(i=0; i<storedGames; i++) {
18070             if(savedDetails[i])
18071                 free(savedDetails[i]);
18072             savedDetails[i] = NULL;
18073         }
18074         for(i=framePtr; i<MAX_MOVES; i++) {
18075                 if(commentList[i]) free(commentList[i]);
18076                 commentList[i] = NULL;
18077         }
18078         framePtr = MAX_MOVES-1;
18079         storedGames = 0;
18080 }
18081
18082 void
18083 LoadVariation (int index, char *text)
18084 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18085         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18086         int level = 0, move;
18087
18088         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18089         // first find outermost bracketing variation
18090         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18091             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18092                 if(*p == '{') wait = '}'; else
18093                 if(*p == '[') wait = ']'; else
18094                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18095                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18096             }
18097             if(*p == wait) wait = NULLCHAR; // closing ]} found
18098             p++;
18099         }
18100         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18101         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18102         end[1] = NULLCHAR; // clip off comment beyond variation
18103         ToNrEvent(currentMove-1);
18104         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18105         // kludge: use ParsePV() to append variation to game
18106         move = currentMove;
18107         ParsePV(start, TRUE, TRUE);
18108         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18109         ClearPremoveHighlights();
18110         CommentPopDown();
18111         ToNrEvent(currentMove+1);
18112 }
18113
18114 void
18115 LoadTheme ()
18116 {
18117     char *p, *q, buf[MSG_SIZ];
18118     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18119         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18120         ParseArgsFromString(buf);
18121         ActivateTheme(TRUE); // also redo colors
18122         return;
18123     }
18124     p = nickName;
18125     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18126     {
18127         int len;
18128         q = appData.themeNames;
18129         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18130       if(appData.useBitmaps) {
18131         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18132                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18133                 appData.liteBackTextureMode,
18134                 appData.darkBackTextureMode );
18135       } else {
18136         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18137                 Col2Text(2),   // lightSquareColor
18138                 Col2Text(3) ); // darkSquareColor
18139       }
18140       if(appData.useBorder) {
18141         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18142                 appData.border);
18143       } else {
18144         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18145       }
18146       if(appData.useFont) {
18147         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18148                 appData.renderPiecesWithFont,
18149                 appData.fontToPieceTable,
18150                 Col2Text(9),    // appData.fontBackColorWhite
18151                 Col2Text(10) ); // appData.fontForeColorBlack
18152       } else {
18153         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18154                 appData.pieceDirectory);
18155         if(!appData.pieceDirectory[0])
18156           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18157                 Col2Text(0),   // whitePieceColor
18158                 Col2Text(1) ); // blackPieceColor
18159       }
18160       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18161                 Col2Text(4),   // highlightSquareColor
18162                 Col2Text(5) ); // premoveHighlightColor
18163         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18164         if(insert != q) insert[-1] = NULLCHAR;
18165         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18166         if(q)   free(q);
18167     }
18168     ActivateTheme(FALSE);
18169 }