f61c15c75a00d5d0c0787f23821af2e3053fc927
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 Boolean abortMatch;
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
276
277 /* States for ics_getting_history */
278 #define H_FALSE 0
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
284
285 /* whosays values for GameEnds */
286 #define GE_ICS 0
287 #define GE_ENGINE 1
288 #define GE_PLAYER 2
289 #define GE_FILE 3
290 #define GE_XBOARD 4
291 #define GE_ENGINE1 5
292 #define GE_ENGINE2 6
293
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
296
297 /* Different types of move when calling RegisterMove */
298 #define CMAIL_MOVE   0
299 #define CMAIL_RESIGN 1
300 #define CMAIL_DRAW   2
301 #define CMAIL_ACCEPT 3
302
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
307
308 /* Telnet protocol constants */
309 #define TN_WILL 0373
310 #define TN_WONT 0374
311 #define TN_DO   0375
312 #define TN_DONT 0376
313 #define TN_IAC  0377
314 #define TN_ECHO 0001
315 #define TN_SGA  0003
316 #define TN_PORT 23
317
318 char*
319 safeStrCpy (char *dst, const char *src, size_t count)
320 { // [HGM] made safe
321   int i;
322   assert( dst != NULL );
323   assert( src != NULL );
324   assert( count > 0 );
325
326   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327   if(  i == count && dst[count-1] != NULLCHAR)
328     {
329       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330       if(appData.debugMode)
331       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
332     }
333
334   return dst;
335 }
336
337 /* Some compiler can't cast u64 to double
338  * This function do the job for us:
339
340  * We use the highest bit for cast, this only
341  * works if the highest bit is not
342  * in use (This should not happen)
343  *
344  * We used this for all compiler
345  */
346 double
347 u64ToDouble (u64 value)
348 {
349   double r;
350   u64 tmp = value & u64Const(0x7fffffffffffffff);
351   r = (double)(s64)tmp;
352   if (value & u64Const(0x8000000000000000))
353        r +=  9.2233720368547758080e18; /* 2^63 */
354  return r;
355 }
356
357 /* Fake up flags for now, as we aren't keeping track of castling
358    availability yet. [HGM] Change of logic: the flag now only
359    indicates the type of castlings allowed by the rule of the game.
360    The actual rights themselves are maintained in the array
361    castlingRights, as part of the game history, and are not probed
362    by this function.
363  */
364 int
365 PosFlags (index)
366 {
367   int flags = F_ALL_CASTLE_OK;
368   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369   switch (gameInfo.variant) {
370   case VariantSuicide:
371     flags &= ~F_ALL_CASTLE_OK;
372   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373     flags |= F_IGNORE_CHECK;
374   case VariantLosers:
375     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376     break;
377   case VariantAtomic:
378     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379     break;
380   case VariantKriegspiel:
381     flags |= F_KRIEGSPIEL_CAPTURE;
382     break;
383   case VariantCapaRandom:
384   case VariantFischeRandom:
385     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386   case VariantNoCastle:
387   case VariantShatranj:
388   case VariantCourier:
389   case VariantMakruk:
390   case VariantGrand:
391     flags &= ~F_ALL_CASTLE_OK;
392     break;
393   default:
394     break;
395   }
396   return flags;
397 }
398
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
401
402 /*
403     [AS] Note: sometimes, the sscanf() function is used to parse the input
404     into a fixed-size buffer. Because of this, we must be prepared to
405     receive strings as long as the size of the input buffer, which is currently
406     set to 4K for Windows and 8K for the rest.
407     So, we must either allocate sufficiently large buffers here, or
408     reduce the size of the input buffer in the input reading part.
409 */
410
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
414
415 ChessProgramState first, second, pairing;
416
417 /* premove variables */
418 int premoveToX = 0;
419 int premoveToY = 0;
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
423 int gotPremove = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
426
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
429
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
457
458 int have_sent_ICS_logon = 0;
459 int movesPerSession;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE, startingEngine = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
471
472 /* animateTraining preserves the state of appData.animate
473  * when Training mode is activated. This allows the
474  * response to be animated when appData.animate == TRUE and
475  * appData.animateDragging == TRUE.
476  */
477 Boolean animateTraining;
478
479 GameInfo gameInfo;
480
481 AppData appData;
482
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char  initialRights[BOARD_FILES];
487 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int   initialRulePlies, FENrulePlies;
489 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
490 int loadFlag = 0;
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
493
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
497 int storedGames = 0;
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
503
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
509
510 ChessSquare  FIDEArray[2][BOARD_FILES] = {
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514         BlackKing, BlackBishop, BlackKnight, BlackRook }
515 };
516
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackKing, BlackKnight, BlackRook }
522 };
523
524 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527     { BlackRook, BlackMan, BlackBishop, BlackQueen,
528         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 };
530
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 };
537
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 };
544
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 };
551
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackMan, BlackFerz,
556         BlackKing, BlackMan, BlackKnight, BlackRook }
557 };
558
559
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 };
567
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 };
574
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 };
581
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 };
588
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 };
595
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 };
602
603 #ifdef GOTHIC
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 };
610 #else // !GOTHIC
611 #define GothicArray CapablancaArray
612 #endif // !GOTHIC
613
614 #ifdef FALCON
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
620 };
621 #else // !FALCON
622 #define FalconArray CapablancaArray
623 #endif // !FALCON
624
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
631
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
638 };
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
642
643
644 Board initialPosition;
645
646
647 /* Convert str to a rating. Checks for special cases of "----",
648
649    "++++", etc. Also strips ()'s */
650 int
651 string_to_rating (char *str)
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats ()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit ()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine (ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions (ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("first"),
744   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 N_("second")
747 };
748
749 void
750 InitEngine (ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789     cps->reload = FALSE;
790
791     /* New features added by Tord: */
792     cps->useFEN960 = FALSE;
793     cps->useOOCastle = TRUE;
794     /* End of new features added by Tord. */
795     cps->fenOverride  = appData.fenOverride[n];
796
797     /* [HGM] time odds: set factor for each machine */
798     cps->timeOdds  = appData.timeOdds[n];
799
800     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801     cps->accumulateTC = appData.accumulateTC[n];
802     cps->maxNrOfSessions = 1;
803
804     /* [HGM] debug */
805     cps->debug = FALSE;
806
807     cps->supportsNPS = UNKNOWN;
808     cps->memSize = FALSE;
809     cps->maxCores = FALSE;
810     cps->egtFormats[0] = NULLCHAR;
811
812     /* [HGM] options */
813     cps->optionSettings  = appData.engOptions[n];
814
815     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816     cps->isUCI = appData.isUCI[n]; /* [AS] */
817     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
818
819     if (appData.protocolVersion[n] > PROTOVER
820         || appData.protocolVersion[n] < 1)
821       {
822         char buf[MSG_SIZ];
823         int len;
824
825         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826                        appData.protocolVersion[n]);
827         if( (len >= MSG_SIZ) && appData.debugMode )
828           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
829
830         DisplayFatalError(buf, 0, 2);
831       }
832     else
833       {
834         cps->protocolVersion = appData.protocolVersion[n];
835       }
836
837     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
838     ParseFeatures(appData.featureDefaults, cps);
839 }
840
841 ChessProgramState *savCps;
842
843 GameMode oldMode;
844
845 void
846 LoadEngine ()
847 {
848     int i;
849     if(WaitForEngine(savCps, LoadEngine)) return;
850     CommonEngineInit(); // recalculate time odds
851     if(gameInfo.variant != StringToVariant(appData.variant)) {
852         // we changed variant when loading the engine; this forces us to reset
853         Reset(TRUE, savCps != &first);
854         oldMode = BeginningOfGame; // to prevent restoring old mode
855     }
856     InitChessProgram(savCps, FALSE);
857     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
858     DisplayMessage("", "");
859     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
861     ThawUI();
862     SetGNUMode();
863     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
864 }
865
866 void
867 ReplaceEngine (ChessProgramState *cps, int n)
868 {
869     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
870     keepInfo = 1;
871     if(oldMode != BeginningOfGame) EditGameEvent();
872     keepInfo = 0;
873     UnloadEngine(cps);
874     appData.noChessProgram = FALSE;
875     appData.clockMode = TRUE;
876     InitEngine(cps, n);
877     UpdateLogos(TRUE);
878     if(n) return; // only startup first engine immediately; second can wait
879     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
880     LoadEngine();
881 }
882
883 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
884 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
885
886 static char resetOptions[] =
887         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
888         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
889         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
890         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
891
892 void
893 FloatToFront(char **list, char *engineLine)
894 {
895     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
896     int i=0;
897     if(appData.recentEngines <= 0) return;
898     TidyProgramName(engineLine, "localhost", tidy+1);
899     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
900     strncpy(buf+1, *list, MSG_SIZ-50);
901     if(p = strstr(buf, tidy)) { // tidy name appears in list
902         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
903         while(*p++ = *++q); // squeeze out
904     }
905     strcat(tidy, buf+1); // put list behind tidy name
906     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
907     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
908     ASSIGN(*list, tidy+1);
909 }
910
911 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
912
913 void
914 Load (ChessProgramState *cps, int i)
915 {
916     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
917     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
918         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
919         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
920         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
921         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
922         appData.firstProtocolVersion = PROTOVER;
923         ParseArgsFromString(buf);
924         SwapEngines(i);
925         ReplaceEngine(cps, i);
926         FloatToFront(&appData.recentEngineList, engineLine);
927         return;
928     }
929     p = engineName;
930     while(q = strchr(p, SLASH)) p = q+1;
931     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
932     if(engineDir[0] != NULLCHAR) {
933         ASSIGN(appData.directory[i], engineDir); p = engineName;
934     } else if(p != engineName) { // derive directory from engine path, when not given
935         p[-1] = 0;
936         ASSIGN(appData.directory[i], engineName);
937         p[-1] = SLASH;
938         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
939     } else { ASSIGN(appData.directory[i], "."); }
940     if(params[0]) {
941         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
942         snprintf(command, MSG_SIZ, "%s %s", p, params);
943         p = command;
944     }
945     ASSIGN(appData.chessProgram[i], p);
946     appData.isUCI[i] = isUCI;
947     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
948     appData.hasOwnBookUCI[i] = hasBook;
949     if(!nickName[0]) useNick = FALSE;
950     if(useNick) ASSIGN(appData.pgnName[i], nickName);
951     if(addToList) {
952         int len;
953         char quote;
954         q = firstChessProgramNames;
955         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
956         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
957         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
958                         quote, p, quote, appData.directory[i],
959                         useNick ? " -fn \"" : "",
960                         useNick ? nickName : "",
961                         useNick ? "\"" : "",
962                         v1 ? " -firstProtocolVersion 1" : "",
963                         hasBook ? "" : " -fNoOwnBookUCI",
964                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
965                         storeVariant ? " -variant " : "",
966                         storeVariant ? VariantName(gameInfo.variant) : "");
967         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
968         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
969         if(insert != q) insert[-1] = NULLCHAR;
970         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
971         if(q)   free(q);
972         FloatToFront(&appData.recentEngineList, buf);
973     }
974     ReplaceEngine(cps, i);
975 }
976
977 void
978 InitTimeControls ()
979 {
980     int matched, min, sec;
981     /*
982      * Parse timeControl resource
983      */
984     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
985                           appData.movesPerSession)) {
986         char buf[MSG_SIZ];
987         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
988         DisplayFatalError(buf, 0, 2);
989     }
990
991     /*
992      * Parse searchTime resource
993      */
994     if (*appData.searchTime != NULLCHAR) {
995         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
996         if (matched == 1) {
997             searchTime = min * 60;
998         } else if (matched == 2) {
999             searchTime = min * 60 + sec;
1000         } else {
1001             char buf[MSG_SIZ];
1002             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1003             DisplayFatalError(buf, 0, 2);
1004         }
1005     }
1006 }
1007
1008 void
1009 InitBackEnd1 ()
1010 {
1011
1012     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1013     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1014
1015     GetTimeMark(&programStartTime);
1016     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1017     appData.seedBase = random() + (random()<<15);
1018     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1019
1020     ClearProgramStats();
1021     programStats.ok_to_send = 1;
1022     programStats.seen_stat = 0;
1023
1024     /*
1025      * Initialize game list
1026      */
1027     ListNew(&gameList);
1028
1029
1030     /*
1031      * Internet chess server status
1032      */
1033     if (appData.icsActive) {
1034         appData.matchMode = FALSE;
1035         appData.matchGames = 0;
1036 #if ZIPPY
1037         appData.noChessProgram = !appData.zippyPlay;
1038 #else
1039         appData.zippyPlay = FALSE;
1040         appData.zippyTalk = FALSE;
1041         appData.noChessProgram = TRUE;
1042 #endif
1043         if (*appData.icsHelper != NULLCHAR) {
1044             appData.useTelnet = TRUE;
1045             appData.telnetProgram = appData.icsHelper;
1046         }
1047     } else {
1048         appData.zippyTalk = appData.zippyPlay = FALSE;
1049     }
1050
1051     /* [AS] Initialize pv info list [HGM] and game state */
1052     {
1053         int i, j;
1054
1055         for( i=0; i<=framePtr; i++ ) {
1056             pvInfoList[i].depth = -1;
1057             boards[i][EP_STATUS] = EP_NONE;
1058             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1059         }
1060     }
1061
1062     InitTimeControls();
1063
1064     /* [AS] Adjudication threshold */
1065     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1066
1067     InitEngine(&first, 0);
1068     InitEngine(&second, 1);
1069     CommonEngineInit();
1070
1071     pairing.which = "pairing"; // pairing engine
1072     pairing.pr = NoProc;
1073     pairing.isr = NULL;
1074     pairing.program = appData.pairingEngine;
1075     pairing.host = "localhost";
1076     pairing.dir = ".";
1077
1078     if (appData.icsActive) {
1079         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1080     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1081         appData.clockMode = FALSE;
1082         first.sendTime = second.sendTime = 0;
1083     }
1084
1085 #if ZIPPY
1086     /* Override some settings from environment variables, for backward
1087        compatibility.  Unfortunately it's not feasible to have the env
1088        vars just set defaults, at least in xboard.  Ugh.
1089     */
1090     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1091       ZippyInit();
1092     }
1093 #endif
1094
1095     if (!appData.icsActive) {
1096       char buf[MSG_SIZ];
1097       int len;
1098
1099       /* Check for variants that are supported only in ICS mode,
1100          or not at all.  Some that are accepted here nevertheless
1101          have bugs; see comments below.
1102       */
1103       VariantClass variant = StringToVariant(appData.variant);
1104       switch (variant) {
1105       case VariantBughouse:     /* need four players and two boards */
1106       case VariantKriegspiel:   /* need to hide pieces and move details */
1107         /* case VariantFischeRandom: (Fabien: moved below) */
1108         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1109         if( (len >= MSG_SIZ) && appData.debugMode )
1110           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1111
1112         DisplayFatalError(buf, 0, 2);
1113         return;
1114
1115       case VariantUnknown:
1116       case VariantLoadable:
1117       case Variant29:
1118       case Variant30:
1119       case Variant31:
1120       case Variant32:
1121       case Variant33:
1122       case Variant34:
1123       case Variant35:
1124       case Variant36:
1125       default:
1126         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1127         if( (len >= MSG_SIZ) && appData.debugMode )
1128           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1129
1130         DisplayFatalError(buf, 0, 2);
1131         return;
1132
1133       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1134       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1135       case VariantGothic:     /* [HGM] should work */
1136       case VariantCapablanca: /* [HGM] should work */
1137       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1138       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1139       case VariantKnightmate: /* [HGM] should work */
1140       case VariantCylinder:   /* [HGM] untested */
1141       case VariantFalcon:     /* [HGM] untested */
1142       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1143                                  offboard interposition not understood */
1144       case VariantNormal:     /* definitely works! */
1145       case VariantWildCastle: /* pieces not automatically shuffled */
1146       case VariantNoCastle:   /* pieces not automatically shuffled */
1147       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1148       case VariantLosers:     /* should work except for win condition,
1149                                  and doesn't know captures are mandatory */
1150       case VariantSuicide:    /* should work except for win condition,
1151                                  and doesn't know captures are mandatory */
1152       case VariantGiveaway:   /* should work except for win condition,
1153                                  and doesn't know captures are mandatory */
1154       case VariantTwoKings:   /* should work */
1155       case VariantAtomic:     /* should work except for win condition */
1156       case Variant3Check:     /* should work except for win condition */
1157       case VariantShatranj:   /* should work except for all win conditions */
1158       case VariantMakruk:     /* should work except for draw countdown */
1159       case VariantBerolina:   /* might work if TestLegality is off */
1160       case VariantCapaRandom: /* should work */
1161       case VariantJanus:      /* should work */
1162       case VariantSuper:      /* experimental */
1163       case VariantGreat:      /* experimental, requires legality testing to be off */
1164       case VariantSChess:     /* S-Chess, should work */
1165       case VariantGrand:      /* should work */
1166       case VariantSpartan:    /* should work */
1167         break;
1168       }
1169     }
1170
1171 }
1172
1173 int
1174 NextIntegerFromString (char ** str, long * value)
1175 {
1176     int result = -1;
1177     char * s = *str;
1178
1179     while( *s == ' ' || *s == '\t' ) {
1180         s++;
1181     }
1182
1183     *value = 0;
1184
1185     if( *s >= '0' && *s <= '9' ) {
1186         while( *s >= '0' && *s <= '9' ) {
1187             *value = *value * 10 + (*s - '0');
1188             s++;
1189         }
1190
1191         result = 0;
1192     }
1193
1194     *str = s;
1195
1196     return result;
1197 }
1198
1199 int
1200 NextTimeControlFromString (char ** str, long * value)
1201 {
1202     long temp;
1203     int result = NextIntegerFromString( str, &temp );
1204
1205     if( result == 0 ) {
1206         *value = temp * 60; /* Minutes */
1207         if( **str == ':' ) {
1208             (*str)++;
1209             result = NextIntegerFromString( str, &temp );
1210             *value += temp; /* Seconds */
1211         }
1212     }
1213
1214     return result;
1215 }
1216
1217 int
1218 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1219 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1220     int result = -1, type = 0; long temp, temp2;
1221
1222     if(**str != ':') return -1; // old params remain in force!
1223     (*str)++;
1224     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1225     if( NextIntegerFromString( str, &temp ) ) return -1;
1226     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1227
1228     if(**str != '/') {
1229         /* time only: incremental or sudden-death time control */
1230         if(**str == '+') { /* increment follows; read it */
1231             (*str)++;
1232             if(**str == '!') type = *(*str)++; // Bronstein TC
1233             if(result = NextIntegerFromString( str, &temp2)) return -1;
1234             *inc = temp2 * 1000;
1235             if(**str == '.') { // read fraction of increment
1236                 char *start = ++(*str);
1237                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1238                 temp2 *= 1000;
1239                 while(start++ < *str) temp2 /= 10;
1240                 *inc += temp2;
1241             }
1242         } else *inc = 0;
1243         *moves = 0; *tc = temp * 1000; *incType = type;
1244         return 0;
1245     }
1246
1247     (*str)++; /* classical time control */
1248     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1249
1250     if(result == 0) {
1251         *moves = temp;
1252         *tc    = temp2 * 1000;
1253         *inc   = 0;
1254         *incType = type;
1255     }
1256     return result;
1257 }
1258
1259 int
1260 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1261 {   /* [HGM] get time to add from the multi-session time-control string */
1262     int incType, moves=1; /* kludge to force reading of first session */
1263     long time, increment;
1264     char *s = tcString;
1265
1266     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1267     do {
1268         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1269         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1270         if(movenr == -1) return time;    /* last move before new session     */
1271         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1272         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1273         if(!moves) return increment;     /* current session is incremental   */
1274         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1275     } while(movenr >= -1);               /* try again for next session       */
1276
1277     return 0; // no new time quota on this move
1278 }
1279
1280 int
1281 ParseTimeControl (char *tc, float ti, int mps)
1282 {
1283   long tc1;
1284   long tc2;
1285   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1286   int min, sec=0;
1287
1288   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1289   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1290       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1291   if(ti > 0) {
1292
1293     if(mps)
1294       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1295     else
1296       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1297   } else {
1298     if(mps)
1299       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1300     else
1301       snprintf(buf, MSG_SIZ, ":%s", mytc);
1302   }
1303   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1304
1305   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1306     return FALSE;
1307   }
1308
1309   if( *tc == '/' ) {
1310     /* Parse second time control */
1311     tc++;
1312
1313     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1314       return FALSE;
1315     }
1316
1317     if( tc2 == 0 ) {
1318       return FALSE;
1319     }
1320
1321     timeControl_2 = tc2 * 1000;
1322   }
1323   else {
1324     timeControl_2 = 0;
1325   }
1326
1327   if( tc1 == 0 ) {
1328     return FALSE;
1329   }
1330
1331   timeControl = tc1 * 1000;
1332
1333   if (ti >= 0) {
1334     timeIncrement = ti * 1000;  /* convert to ms */
1335     movesPerSession = 0;
1336   } else {
1337     timeIncrement = 0;
1338     movesPerSession = mps;
1339   }
1340   return TRUE;
1341 }
1342
1343 void
1344 InitBackEnd2 ()
1345 {
1346     if (appData.debugMode) {
1347         fprintf(debugFP, "%s\n", programVersion);
1348     }
1349     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1350
1351     set_cont_sequence(appData.wrapContSeq);
1352     if (appData.matchGames > 0) {
1353         appData.matchMode = TRUE;
1354     } else if (appData.matchMode) {
1355         appData.matchGames = 1;
1356     }
1357     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1358         appData.matchGames = appData.sameColorGames;
1359     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1360         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1361         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1362     }
1363     Reset(TRUE, FALSE);
1364     if (appData.noChessProgram || first.protocolVersion == 1) {
1365       InitBackEnd3();
1366     } else {
1367       /* kludge: allow timeout for initial "feature" commands */
1368       FreezeUI();
1369       DisplayMessage("", _("Starting chess program"));
1370       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1371     }
1372 }
1373
1374 int
1375 CalculateIndex (int index, int gameNr)
1376 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1377     int res;
1378     if(index > 0) return index; // fixed nmber
1379     if(index == 0) return 1;
1380     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1381     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1382     return res;
1383 }
1384
1385 int
1386 LoadGameOrPosition (int gameNr)
1387 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1388     if (*appData.loadGameFile != NULLCHAR) {
1389         if (!LoadGameFromFile(appData.loadGameFile,
1390                 CalculateIndex(appData.loadGameIndex, gameNr),
1391                               appData.loadGameFile, FALSE)) {
1392             DisplayFatalError(_("Bad game file"), 0, 1);
1393             return 0;
1394         }
1395     } else if (*appData.loadPositionFile != NULLCHAR) {
1396         if (!LoadPositionFromFile(appData.loadPositionFile,
1397                 CalculateIndex(appData.loadPositionIndex, gameNr),
1398                                   appData.loadPositionFile)) {
1399             DisplayFatalError(_("Bad position file"), 0, 1);
1400             return 0;
1401         }
1402     }
1403     return 1;
1404 }
1405
1406 void
1407 ReserveGame (int gameNr, char resChar)
1408 {
1409     FILE *tf = fopen(appData.tourneyFile, "r+");
1410     char *p, *q, c, buf[MSG_SIZ];
1411     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1412     safeStrCpy(buf, lastMsg, MSG_SIZ);
1413     DisplayMessage(_("Pick new game"), "");
1414     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1415     ParseArgsFromFile(tf);
1416     p = q = appData.results;
1417     if(appData.debugMode) {
1418       char *r = appData.participants;
1419       fprintf(debugFP, "results = '%s'\n", p);
1420       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1421       fprintf(debugFP, "\n");
1422     }
1423     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1424     nextGame = q - p;
1425     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1426     safeStrCpy(q, p, strlen(p) + 2);
1427     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1428     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1429     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1430         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1431         q[nextGame] = '*';
1432     }
1433     fseek(tf, -(strlen(p)+4), SEEK_END);
1434     c = fgetc(tf);
1435     if(c != '"') // depending on DOS or Unix line endings we can be one off
1436          fseek(tf, -(strlen(p)+2), SEEK_END);
1437     else fseek(tf, -(strlen(p)+3), SEEK_END);
1438     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1439     DisplayMessage(buf, "");
1440     free(p); appData.results = q;
1441     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1442        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1443       int round = appData.defaultMatchGames * appData.tourneyType;
1444       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1445          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1446         UnloadEngine(&first);  // next game belongs to other pairing;
1447         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1448     }
1449     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1450 }
1451
1452 void
1453 MatchEvent (int mode)
1454 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1455         int dummy;
1456         if(matchMode) { // already in match mode: switch it off
1457             abortMatch = TRUE;
1458             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1459             return;
1460         }
1461 //      if(gameMode != BeginningOfGame) {
1462 //          DisplayError(_("You can only start a match from the initial position."), 0);
1463 //          return;
1464 //      }
1465         abortMatch = FALSE;
1466         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1467         /* Set up machine vs. machine match */
1468         nextGame = 0;
1469         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1470         if(appData.tourneyFile[0]) {
1471             ReserveGame(-1, 0);
1472             if(nextGame > appData.matchGames) {
1473                 char buf[MSG_SIZ];
1474                 if(strchr(appData.results, '*') == NULL) {
1475                     FILE *f;
1476                     appData.tourneyCycles++;
1477                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1478                         fclose(f);
1479                         NextTourneyGame(-1, &dummy);
1480                         ReserveGame(-1, 0);
1481                         if(nextGame <= appData.matchGames) {
1482                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1483                             matchMode = mode;
1484                             ScheduleDelayedEvent(NextMatchGame, 10000);
1485                             return;
1486                         }
1487                     }
1488                 }
1489                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1490                 DisplayError(buf, 0);
1491                 appData.tourneyFile[0] = 0;
1492                 return;
1493             }
1494         } else
1495         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1496             DisplayFatalError(_("Can't have a match with no chess programs"),
1497                               0, 2);
1498             return;
1499         }
1500         matchMode = mode;
1501         matchGame = roundNr = 1;
1502         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1503         NextMatchGame();
1504 }
1505
1506 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1507
1508 void
1509 InitBackEnd3 P((void))
1510 {
1511     GameMode initialMode;
1512     char buf[MSG_SIZ];
1513     int err, len;
1514
1515     InitChessProgram(&first, startedFromSetupPosition);
1516
1517     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1518         free(programVersion);
1519         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1520         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1521         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1522     }
1523
1524     if (appData.icsActive) {
1525 #ifdef WIN32
1526         /* [DM] Make a console window if needed [HGM] merged ifs */
1527         ConsoleCreate();
1528 #endif
1529         err = establish();
1530         if (err != 0)
1531           {
1532             if (*appData.icsCommPort != NULLCHAR)
1533               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1534                              appData.icsCommPort);
1535             else
1536               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1537                         appData.icsHost, appData.icsPort);
1538
1539             if( (len >= MSG_SIZ) && appData.debugMode )
1540               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1541
1542             DisplayFatalError(buf, err, 1);
1543             return;
1544         }
1545         SetICSMode();
1546         telnetISR =
1547           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1548         fromUserISR =
1549           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1550         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1551             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1552     } else if (appData.noChessProgram) {
1553         SetNCPMode();
1554     } else {
1555         SetGNUMode();
1556     }
1557
1558     if (*appData.cmailGameName != NULLCHAR) {
1559         SetCmailMode();
1560         OpenLoopback(&cmailPR);
1561         cmailISR =
1562           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1563     }
1564
1565     ThawUI();
1566     DisplayMessage("", "");
1567     if (StrCaseCmp(appData.initialMode, "") == 0) {
1568       initialMode = BeginningOfGame;
1569       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1570         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1571         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1572         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1573         ModeHighlight();
1574       }
1575     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1576       initialMode = TwoMachinesPlay;
1577     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1578       initialMode = AnalyzeFile;
1579     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1580       initialMode = AnalyzeMode;
1581     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1582       initialMode = MachinePlaysWhite;
1583     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1584       initialMode = MachinePlaysBlack;
1585     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1586       initialMode = EditGame;
1587     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1588       initialMode = EditPosition;
1589     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1590       initialMode = Training;
1591     } else {
1592       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1593       if( (len >= MSG_SIZ) && appData.debugMode )
1594         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1595
1596       DisplayFatalError(buf, 0, 2);
1597       return;
1598     }
1599
1600     if (appData.matchMode) {
1601         if(appData.tourneyFile[0]) { // start tourney from command line
1602             FILE *f;
1603             if(f = fopen(appData.tourneyFile, "r")) {
1604                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1605                 fclose(f);
1606                 appData.clockMode = TRUE;
1607                 SetGNUMode();
1608             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1609         }
1610         MatchEvent(TRUE);
1611     } else if (*appData.cmailGameName != NULLCHAR) {
1612         /* Set up cmail mode */
1613         ReloadCmailMsgEvent(TRUE);
1614     } else {
1615         /* Set up other modes */
1616         if (initialMode == AnalyzeFile) {
1617           if (*appData.loadGameFile == NULLCHAR) {
1618             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1619             return;
1620           }
1621         }
1622         if (*appData.loadGameFile != NULLCHAR) {
1623             (void) LoadGameFromFile(appData.loadGameFile,
1624                                     appData.loadGameIndex,
1625                                     appData.loadGameFile, TRUE);
1626         } else if (*appData.loadPositionFile != NULLCHAR) {
1627             (void) LoadPositionFromFile(appData.loadPositionFile,
1628                                         appData.loadPositionIndex,
1629                                         appData.loadPositionFile);
1630             /* [HGM] try to make self-starting even after FEN load */
1631             /* to allow automatic setup of fairy variants with wtm */
1632             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1633                 gameMode = BeginningOfGame;
1634                 setboardSpoiledMachineBlack = 1;
1635             }
1636             /* [HGM] loadPos: make that every new game uses the setup */
1637             /* from file as long as we do not switch variant          */
1638             if(!blackPlaysFirst) {
1639                 startedFromPositionFile = TRUE;
1640                 CopyBoard(filePosition, boards[0]);
1641             }
1642         }
1643         if (initialMode == AnalyzeMode) {
1644           if (appData.noChessProgram) {
1645             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1646             return;
1647           }
1648           if (appData.icsActive) {
1649             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1650             return;
1651           }
1652           AnalyzeModeEvent();
1653         } else if (initialMode == AnalyzeFile) {
1654           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1655           ShowThinkingEvent();
1656           AnalyzeFileEvent();
1657           AnalysisPeriodicEvent(1);
1658         } else if (initialMode == MachinePlaysWhite) {
1659           if (appData.noChessProgram) {
1660             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1661                               0, 2);
1662             return;
1663           }
1664           if (appData.icsActive) {
1665             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1666                               0, 2);
1667             return;
1668           }
1669           MachineWhiteEvent();
1670         } else if (initialMode == MachinePlaysBlack) {
1671           if (appData.noChessProgram) {
1672             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1673                               0, 2);
1674             return;
1675           }
1676           if (appData.icsActive) {
1677             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1678                               0, 2);
1679             return;
1680           }
1681           MachineBlackEvent();
1682         } else if (initialMode == TwoMachinesPlay) {
1683           if (appData.noChessProgram) {
1684             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1685                               0, 2);
1686             return;
1687           }
1688           if (appData.icsActive) {
1689             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1690                               0, 2);
1691             return;
1692           }
1693           TwoMachinesEvent();
1694         } else if (initialMode == EditGame) {
1695           EditGameEvent();
1696         } else if (initialMode == EditPosition) {
1697           EditPositionEvent();
1698         } else if (initialMode == Training) {
1699           if (*appData.loadGameFile == NULLCHAR) {
1700             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1701             return;
1702           }
1703           TrainingEvent();
1704         }
1705     }
1706 }
1707
1708 void
1709 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1710 {
1711     DisplayBook(current+1);
1712
1713     MoveHistorySet( movelist, first, last, current, pvInfoList );
1714
1715     EvalGraphSet( first, last, current, pvInfoList );
1716
1717     MakeEngineOutputTitle();
1718 }
1719
1720 /*
1721  * Establish will establish a contact to a remote host.port.
1722  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1723  *  used to talk to the host.
1724  * Returns 0 if okay, error code if not.
1725  */
1726 int
1727 establish ()
1728 {
1729     char buf[MSG_SIZ];
1730
1731     if (*appData.icsCommPort != NULLCHAR) {
1732         /* Talk to the host through a serial comm port */
1733         return OpenCommPort(appData.icsCommPort, &icsPR);
1734
1735     } else if (*appData.gateway != NULLCHAR) {
1736         if (*appData.remoteShell == NULLCHAR) {
1737             /* Use the rcmd protocol to run telnet program on a gateway host */
1738             snprintf(buf, sizeof(buf), "%s %s %s",
1739                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1740             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1741
1742         } else {
1743             /* Use the rsh program to run telnet program on a gateway host */
1744             if (*appData.remoteUser == NULLCHAR) {
1745                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1746                         appData.gateway, appData.telnetProgram,
1747                         appData.icsHost, appData.icsPort);
1748             } else {
1749                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1750                         appData.remoteShell, appData.gateway,
1751                         appData.remoteUser, appData.telnetProgram,
1752                         appData.icsHost, appData.icsPort);
1753             }
1754             return StartChildProcess(buf, "", &icsPR);
1755
1756         }
1757     } else if (appData.useTelnet) {
1758         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1759
1760     } else {
1761         /* TCP socket interface differs somewhat between
1762            Unix and NT; handle details in the front end.
1763            */
1764         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1765     }
1766 }
1767
1768 void
1769 EscapeExpand (char *p, char *q)
1770 {       // [HGM] initstring: routine to shape up string arguments
1771         while(*p++ = *q++) if(p[-1] == '\\')
1772             switch(*q++) {
1773                 case 'n': p[-1] = '\n'; break;
1774                 case 'r': p[-1] = '\r'; break;
1775                 case 't': p[-1] = '\t'; break;
1776                 case '\\': p[-1] = '\\'; break;
1777                 case 0: *p = 0; return;
1778                 default: p[-1] = q[-1]; break;
1779             }
1780 }
1781
1782 void
1783 show_bytes (FILE *fp, char *buf, int count)
1784 {
1785     while (count--) {
1786         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1787             fprintf(fp, "\\%03o", *buf & 0xff);
1788         } else {
1789             putc(*buf, fp);
1790         }
1791         buf++;
1792     }
1793     fflush(fp);
1794 }
1795
1796 /* Returns an errno value */
1797 int
1798 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1799 {
1800     char buf[8192], *p, *q, *buflim;
1801     int left, newcount, outcount;
1802
1803     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1804         *appData.gateway != NULLCHAR) {
1805         if (appData.debugMode) {
1806             fprintf(debugFP, ">ICS: ");
1807             show_bytes(debugFP, message, count);
1808             fprintf(debugFP, "\n");
1809         }
1810         return OutputToProcess(pr, message, count, outError);
1811     }
1812
1813     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1814     p = message;
1815     q = buf;
1816     left = count;
1817     newcount = 0;
1818     while (left) {
1819         if (q >= buflim) {
1820             if (appData.debugMode) {
1821                 fprintf(debugFP, ">ICS: ");
1822                 show_bytes(debugFP, buf, newcount);
1823                 fprintf(debugFP, "\n");
1824             }
1825             outcount = OutputToProcess(pr, buf, newcount, outError);
1826             if (outcount < newcount) return -1; /* to be sure */
1827             q = buf;
1828             newcount = 0;
1829         }
1830         if (*p == '\n') {
1831             *q++ = '\r';
1832             newcount++;
1833         } else if (((unsigned char) *p) == TN_IAC) {
1834             *q++ = (char) TN_IAC;
1835             newcount ++;
1836         }
1837         *q++ = *p++;
1838         newcount++;
1839         left--;
1840     }
1841     if (appData.debugMode) {
1842         fprintf(debugFP, ">ICS: ");
1843         show_bytes(debugFP, buf, newcount);
1844         fprintf(debugFP, "\n");
1845     }
1846     outcount = OutputToProcess(pr, buf, newcount, outError);
1847     if (outcount < newcount) return -1; /* to be sure */
1848     return count;
1849 }
1850
1851 void
1852 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1853 {
1854     int outError, outCount;
1855     static int gotEof = 0;
1856     static FILE *ini;
1857
1858     /* Pass data read from player on to ICS */
1859     if (count > 0) {
1860         gotEof = 0;
1861         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1862         if (outCount < count) {
1863             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1864         }
1865         if(have_sent_ICS_logon == 2) {
1866           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1867             fprintf(ini, "%s", message);
1868             have_sent_ICS_logon = 3;
1869           } else
1870             have_sent_ICS_logon = 1;
1871         } else if(have_sent_ICS_logon == 3) {
1872             fprintf(ini, "%s", message);
1873             fclose(ini);
1874           have_sent_ICS_logon = 1;
1875         }
1876     } else if (count < 0) {
1877         RemoveInputSource(isr);
1878         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1879     } else if (gotEof++ > 0) {
1880         RemoveInputSource(isr);
1881         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1882     }
1883 }
1884
1885 void
1886 KeepAlive ()
1887 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1888     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1889     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1890     SendToICS("date\n");
1891     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1892 }
1893
1894 /* added routine for printf style output to ics */
1895 void
1896 ics_printf (char *format, ...)
1897 {
1898     char buffer[MSG_SIZ];
1899     va_list args;
1900
1901     va_start(args, format);
1902     vsnprintf(buffer, sizeof(buffer), format, args);
1903     buffer[sizeof(buffer)-1] = '\0';
1904     SendToICS(buffer);
1905     va_end(args);
1906 }
1907
1908 void
1909 SendToICS (char *s)
1910 {
1911     int count, outCount, outError;
1912
1913     if (icsPR == NoProc) return;
1914
1915     count = strlen(s);
1916     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1917     if (outCount < count) {
1918         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1919     }
1920 }
1921
1922 /* This is used for sending logon scripts to the ICS. Sending
1923    without a delay causes problems when using timestamp on ICC
1924    (at least on my machine). */
1925 void
1926 SendToICSDelayed (char *s, long msdelay)
1927 {
1928     int count, outCount, outError;
1929
1930     if (icsPR == NoProc) return;
1931
1932     count = strlen(s);
1933     if (appData.debugMode) {
1934         fprintf(debugFP, ">ICS: ");
1935         show_bytes(debugFP, s, count);
1936         fprintf(debugFP, "\n");
1937     }
1938     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1939                                       msdelay);
1940     if (outCount < count) {
1941         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1942     }
1943 }
1944
1945
1946 /* Remove all highlighting escape sequences in s
1947    Also deletes any suffix starting with '('
1948    */
1949 char *
1950 StripHighlightAndTitle (char *s)
1951 {
1952     static char retbuf[MSG_SIZ];
1953     char *p = retbuf;
1954
1955     while (*s != NULLCHAR) {
1956         while (*s == '\033') {
1957             while (*s != NULLCHAR && !isalpha(*s)) s++;
1958             if (*s != NULLCHAR) s++;
1959         }
1960         while (*s != NULLCHAR && *s != '\033') {
1961             if (*s == '(' || *s == '[') {
1962                 *p = NULLCHAR;
1963                 return retbuf;
1964             }
1965             *p++ = *s++;
1966         }
1967     }
1968     *p = NULLCHAR;
1969     return retbuf;
1970 }
1971
1972 /* Remove all highlighting escape sequences in s */
1973 char *
1974 StripHighlight (char *s)
1975 {
1976     static char retbuf[MSG_SIZ];
1977     char *p = retbuf;
1978
1979     while (*s != NULLCHAR) {
1980         while (*s == '\033') {
1981             while (*s != NULLCHAR && !isalpha(*s)) s++;
1982             if (*s != NULLCHAR) s++;
1983         }
1984         while (*s != NULLCHAR && *s != '\033') {
1985             *p++ = *s++;
1986         }
1987     }
1988     *p = NULLCHAR;
1989     return retbuf;
1990 }
1991
1992 char *variantNames[] = VARIANT_NAMES;
1993 char *
1994 VariantName (VariantClass v)
1995 {
1996     return variantNames[v];
1997 }
1998
1999
2000 /* Identify a variant from the strings the chess servers use or the
2001    PGN Variant tag names we use. */
2002 VariantClass
2003 StringToVariant (char *e)
2004 {
2005     char *p;
2006     int wnum = -1;
2007     VariantClass v = VariantNormal;
2008     int i, found = FALSE;
2009     char buf[MSG_SIZ];
2010     int len;
2011
2012     if (!e) return v;
2013
2014     /* [HGM] skip over optional board-size prefixes */
2015     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2016         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2017         while( *e++ != '_');
2018     }
2019
2020     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2021         v = VariantNormal;
2022         found = TRUE;
2023     } else
2024     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2025       if (StrCaseStr(e, variantNames[i])) {
2026         v = (VariantClass) i;
2027         found = TRUE;
2028         break;
2029       }
2030     }
2031
2032     if (!found) {
2033       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2034           || StrCaseStr(e, "wild/fr")
2035           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2036         v = VariantFischeRandom;
2037       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2038                  (i = 1, p = StrCaseStr(e, "w"))) {
2039         p += i;
2040         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2041         if (isdigit(*p)) {
2042           wnum = atoi(p);
2043         } else {
2044           wnum = -1;
2045         }
2046         switch (wnum) {
2047         case 0: /* FICS only, actually */
2048         case 1:
2049           /* Castling legal even if K starts on d-file */
2050           v = VariantWildCastle;
2051           break;
2052         case 2:
2053         case 3:
2054         case 4:
2055           /* Castling illegal even if K & R happen to start in
2056              normal positions. */
2057           v = VariantNoCastle;
2058           break;
2059         case 5:
2060         case 7:
2061         case 8:
2062         case 10:
2063         case 11:
2064         case 12:
2065         case 13:
2066         case 14:
2067         case 15:
2068         case 18:
2069         case 19:
2070           /* Castling legal iff K & R start in normal positions */
2071           v = VariantNormal;
2072           break;
2073         case 6:
2074         case 20:
2075         case 21:
2076           /* Special wilds for position setup; unclear what to do here */
2077           v = VariantLoadable;
2078           break;
2079         case 9:
2080           /* Bizarre ICC game */
2081           v = VariantTwoKings;
2082           break;
2083         case 16:
2084           v = VariantKriegspiel;
2085           break;
2086         case 17:
2087           v = VariantLosers;
2088           break;
2089         case 22:
2090           v = VariantFischeRandom;
2091           break;
2092         case 23:
2093           v = VariantCrazyhouse;
2094           break;
2095         case 24:
2096           v = VariantBughouse;
2097           break;
2098         case 25:
2099           v = Variant3Check;
2100           break;
2101         case 26:
2102           /* Not quite the same as FICS suicide! */
2103           v = VariantGiveaway;
2104           break;
2105         case 27:
2106           v = VariantAtomic;
2107           break;
2108         case 28:
2109           v = VariantShatranj;
2110           break;
2111
2112         /* Temporary names for future ICC types.  The name *will* change in
2113            the next xboard/WinBoard release after ICC defines it. */
2114         case 29:
2115           v = Variant29;
2116           break;
2117         case 30:
2118           v = Variant30;
2119           break;
2120         case 31:
2121           v = Variant31;
2122           break;
2123         case 32:
2124           v = Variant32;
2125           break;
2126         case 33:
2127           v = Variant33;
2128           break;
2129         case 34:
2130           v = Variant34;
2131           break;
2132         case 35:
2133           v = Variant35;
2134           break;
2135         case 36:
2136           v = Variant36;
2137           break;
2138         case 37:
2139           v = VariantShogi;
2140           break;
2141         case 38:
2142           v = VariantXiangqi;
2143           break;
2144         case 39:
2145           v = VariantCourier;
2146           break;
2147         case 40:
2148           v = VariantGothic;
2149           break;
2150         case 41:
2151           v = VariantCapablanca;
2152           break;
2153         case 42:
2154           v = VariantKnightmate;
2155           break;
2156         case 43:
2157           v = VariantFairy;
2158           break;
2159         case 44:
2160           v = VariantCylinder;
2161           break;
2162         case 45:
2163           v = VariantFalcon;
2164           break;
2165         case 46:
2166           v = VariantCapaRandom;
2167           break;
2168         case 47:
2169           v = VariantBerolina;
2170           break;
2171         case 48:
2172           v = VariantJanus;
2173           break;
2174         case 49:
2175           v = VariantSuper;
2176           break;
2177         case 50:
2178           v = VariantGreat;
2179           break;
2180         case -1:
2181           /* Found "wild" or "w" in the string but no number;
2182              must assume it's normal chess. */
2183           v = VariantNormal;
2184           break;
2185         default:
2186           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2187           if( (len >= MSG_SIZ) && appData.debugMode )
2188             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2189
2190           DisplayError(buf, 0);
2191           v = VariantUnknown;
2192           break;
2193         }
2194       }
2195     }
2196     if (appData.debugMode) {
2197       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2198               e, wnum, VariantName(v));
2199     }
2200     return v;
2201 }
2202
2203 static int leftover_start = 0, leftover_len = 0;
2204 char star_match[STAR_MATCH_N][MSG_SIZ];
2205
2206 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2207    advance *index beyond it, and set leftover_start to the new value of
2208    *index; else return FALSE.  If pattern contains the character '*', it
2209    matches any sequence of characters not containing '\r', '\n', or the
2210    character following the '*' (if any), and the matched sequence(s) are
2211    copied into star_match.
2212    */
2213 int
2214 looking_at ( char *buf, int *index, char *pattern)
2215 {
2216     char *bufp = &buf[*index], *patternp = pattern;
2217     int star_count = 0;
2218     char *matchp = star_match[0];
2219
2220     for (;;) {
2221         if (*patternp == NULLCHAR) {
2222             *index = leftover_start = bufp - buf;
2223             *matchp = NULLCHAR;
2224             return TRUE;
2225         }
2226         if (*bufp == NULLCHAR) return FALSE;
2227         if (*patternp == '*') {
2228             if (*bufp == *(patternp + 1)) {
2229                 *matchp = NULLCHAR;
2230                 matchp = star_match[++star_count];
2231                 patternp += 2;
2232                 bufp++;
2233                 continue;
2234             } else if (*bufp == '\n' || *bufp == '\r') {
2235                 patternp++;
2236                 if (*patternp == NULLCHAR)
2237                   continue;
2238                 else
2239                   return FALSE;
2240             } else {
2241                 *matchp++ = *bufp++;
2242                 continue;
2243             }
2244         }
2245         if (*patternp != *bufp) return FALSE;
2246         patternp++;
2247         bufp++;
2248     }
2249 }
2250
2251 void
2252 SendToPlayer (char *data, int length)
2253 {
2254     int error, outCount;
2255     outCount = OutputToProcess(NoProc, data, length, &error);
2256     if (outCount < length) {
2257         DisplayFatalError(_("Error writing to display"), error, 1);
2258     }
2259 }
2260
2261 void
2262 PackHolding (char packed[], char *holding)
2263 {
2264     char *p = holding;
2265     char *q = packed;
2266     int runlength = 0;
2267     int curr = 9999;
2268     do {
2269         if (*p == curr) {
2270             runlength++;
2271         } else {
2272             switch (runlength) {
2273               case 0:
2274                 break;
2275               case 1:
2276                 *q++ = curr;
2277                 break;
2278               case 2:
2279                 *q++ = curr;
2280                 *q++ = curr;
2281                 break;
2282               default:
2283                 sprintf(q, "%d", runlength);
2284                 while (*q) q++;
2285                 *q++ = curr;
2286                 break;
2287             }
2288             runlength = 1;
2289             curr = *p;
2290         }
2291     } while (*p++);
2292     *q = NULLCHAR;
2293 }
2294
2295 /* Telnet protocol requests from the front end */
2296 void
2297 TelnetRequest (unsigned char ddww, unsigned char option)
2298 {
2299     unsigned char msg[3];
2300     int outCount, outError;
2301
2302     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2303
2304     if (appData.debugMode) {
2305         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2306         switch (ddww) {
2307           case TN_DO:
2308             ddwwStr = "DO";
2309             break;
2310           case TN_DONT:
2311             ddwwStr = "DONT";
2312             break;
2313           case TN_WILL:
2314             ddwwStr = "WILL";
2315             break;
2316           case TN_WONT:
2317             ddwwStr = "WONT";
2318             break;
2319           default:
2320             ddwwStr = buf1;
2321             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2322             break;
2323         }
2324         switch (option) {
2325           case TN_ECHO:
2326             optionStr = "ECHO";
2327             break;
2328           default:
2329             optionStr = buf2;
2330             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2331             break;
2332         }
2333         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2334     }
2335     msg[0] = TN_IAC;
2336     msg[1] = ddww;
2337     msg[2] = option;
2338     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2339     if (outCount < 3) {
2340         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2341     }
2342 }
2343
2344 void
2345 DoEcho ()
2346 {
2347     if (!appData.icsActive) return;
2348     TelnetRequest(TN_DO, TN_ECHO);
2349 }
2350
2351 void
2352 DontEcho ()
2353 {
2354     if (!appData.icsActive) return;
2355     TelnetRequest(TN_DONT, TN_ECHO);
2356 }
2357
2358 void
2359 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2360 {
2361     /* put the holdings sent to us by the server on the board holdings area */
2362     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2363     char p;
2364     ChessSquare piece;
2365
2366     if(gameInfo.holdingsWidth < 2)  return;
2367     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2368         return; // prevent overwriting by pre-board holdings
2369
2370     if( (int)lowestPiece >= BlackPawn ) {
2371         holdingsColumn = 0;
2372         countsColumn = 1;
2373         holdingsStartRow = BOARD_HEIGHT-1;
2374         direction = -1;
2375     } else {
2376         holdingsColumn = BOARD_WIDTH-1;
2377         countsColumn = BOARD_WIDTH-2;
2378         holdingsStartRow = 0;
2379         direction = 1;
2380     }
2381
2382     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2383         board[i][holdingsColumn] = EmptySquare;
2384         board[i][countsColumn]   = (ChessSquare) 0;
2385     }
2386     while( (p=*holdings++) != NULLCHAR ) {
2387         piece = CharToPiece( ToUpper(p) );
2388         if(piece == EmptySquare) continue;
2389         /*j = (int) piece - (int) WhitePawn;*/
2390         j = PieceToNumber(piece);
2391         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2392         if(j < 0) continue;               /* should not happen */
2393         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2394         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2395         board[holdingsStartRow+j*direction][countsColumn]++;
2396     }
2397 }
2398
2399
2400 void
2401 VariantSwitch (Board board, VariantClass newVariant)
2402 {
2403    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2404    static Board oldBoard;
2405
2406    startedFromPositionFile = FALSE;
2407    if(gameInfo.variant == newVariant) return;
2408
2409    /* [HGM] This routine is called each time an assignment is made to
2410     * gameInfo.variant during a game, to make sure the board sizes
2411     * are set to match the new variant. If that means adding or deleting
2412     * holdings, we shift the playing board accordingly
2413     * This kludge is needed because in ICS observe mode, we get boards
2414     * of an ongoing game without knowing the variant, and learn about the
2415     * latter only later. This can be because of the move list we requested,
2416     * in which case the game history is refilled from the beginning anyway,
2417     * but also when receiving holdings of a crazyhouse game. In the latter
2418     * case we want to add those holdings to the already received position.
2419     */
2420
2421
2422    if (appData.debugMode) {
2423      fprintf(debugFP, "Switch board from %s to %s\n",
2424              VariantName(gameInfo.variant), VariantName(newVariant));
2425      setbuf(debugFP, NULL);
2426    }
2427    shuffleOpenings = 0;       /* [HGM] shuffle */
2428    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2429    switch(newVariant)
2430      {
2431      case VariantShogi:
2432        newWidth = 9;  newHeight = 9;
2433        gameInfo.holdingsSize = 7;
2434      case VariantBughouse:
2435      case VariantCrazyhouse:
2436        newHoldingsWidth = 2; break;
2437      case VariantGreat:
2438        newWidth = 10;
2439      case VariantSuper:
2440        newHoldingsWidth = 2;
2441        gameInfo.holdingsSize = 8;
2442        break;
2443      case VariantGothic:
2444      case VariantCapablanca:
2445      case VariantCapaRandom:
2446        newWidth = 10;
2447      default:
2448        newHoldingsWidth = gameInfo.holdingsSize = 0;
2449      };
2450
2451    if(newWidth  != gameInfo.boardWidth  ||
2452       newHeight != gameInfo.boardHeight ||
2453       newHoldingsWidth != gameInfo.holdingsWidth ) {
2454
2455      /* shift position to new playing area, if needed */
2456      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2457        for(i=0; i<BOARD_HEIGHT; i++)
2458          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2459            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2460              board[i][j];
2461        for(i=0; i<newHeight; i++) {
2462          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2463          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2464        }
2465      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2466        for(i=0; i<BOARD_HEIGHT; i++)
2467          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2468            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2469              board[i][j];
2470      }
2471      board[HOLDINGS_SET] = 0;
2472      gameInfo.boardWidth  = newWidth;
2473      gameInfo.boardHeight = newHeight;
2474      gameInfo.holdingsWidth = newHoldingsWidth;
2475      gameInfo.variant = newVariant;
2476      InitDrawingSizes(-2, 0);
2477    } else gameInfo.variant = newVariant;
2478    CopyBoard(oldBoard, board);   // remember correctly formatted board
2479      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2480    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2481 }
2482
2483 static int loggedOn = FALSE;
2484
2485 /*-- Game start info cache: --*/
2486 int gs_gamenum;
2487 char gs_kind[MSG_SIZ];
2488 static char player1Name[128] = "";
2489 static char player2Name[128] = "";
2490 static char cont_seq[] = "\n\\   ";
2491 static int player1Rating = -1;
2492 static int player2Rating = -1;
2493 /*----------------------------*/
2494
2495 ColorClass curColor = ColorNormal;
2496 int suppressKibitz = 0;
2497
2498 // [HGM] seekgraph
2499 Boolean soughtPending = FALSE;
2500 Boolean seekGraphUp;
2501 #define MAX_SEEK_ADS 200
2502 #define SQUARE 0x80
2503 char *seekAdList[MAX_SEEK_ADS];
2504 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2505 float tcList[MAX_SEEK_ADS];
2506 char colorList[MAX_SEEK_ADS];
2507 int nrOfSeekAds = 0;
2508 int minRating = 1010, maxRating = 2800;
2509 int hMargin = 10, vMargin = 20, h, w;
2510 extern int squareSize, lineGap;
2511
2512 void
2513 PlotSeekAd (int i)
2514 {
2515         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2516         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2517         if(r < minRating+100 && r >=0 ) r = minRating+100;
2518         if(r > maxRating) r = maxRating;
2519         if(tc < 1.f) tc = 1.f;
2520         if(tc > 95.f) tc = 95.f;
2521         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2522         y = ((double)r - minRating)/(maxRating - minRating)
2523             * (h-vMargin-squareSize/8-1) + vMargin;
2524         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2525         if(strstr(seekAdList[i], " u ")) color = 1;
2526         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2527            !strstr(seekAdList[i], "bullet") &&
2528            !strstr(seekAdList[i], "blitz") &&
2529            !strstr(seekAdList[i], "standard") ) color = 2;
2530         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2531         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2532 }
2533
2534 void
2535 PlotSingleSeekAd (int i)
2536 {
2537         PlotSeekAd(i);
2538 }
2539
2540 void
2541 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2542 {
2543         char buf[MSG_SIZ], *ext = "";
2544         VariantClass v = StringToVariant(type);
2545         if(strstr(type, "wild")) {
2546             ext = type + 4; // append wild number
2547             if(v == VariantFischeRandom) type = "chess960"; else
2548             if(v == VariantLoadable) type = "setup"; else
2549             type = VariantName(v);
2550         }
2551         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2552         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2553             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2554             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2555             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2556             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2557             seekNrList[nrOfSeekAds] = nr;
2558             zList[nrOfSeekAds] = 0;
2559             seekAdList[nrOfSeekAds++] = StrSave(buf);
2560             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2561         }
2562 }
2563
2564 void
2565 EraseSeekDot (int i)
2566 {
2567     int x = xList[i], y = yList[i], d=squareSize/4, k;
2568     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2569     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2570     // now replot every dot that overlapped
2571     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2572         int xx = xList[k], yy = yList[k];
2573         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2574             DrawSeekDot(xx, yy, colorList[k]);
2575     }
2576 }
2577
2578 void
2579 RemoveSeekAd (int nr)
2580 {
2581         int i;
2582         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2583             EraseSeekDot(i);
2584             if(seekAdList[i]) free(seekAdList[i]);
2585             seekAdList[i] = seekAdList[--nrOfSeekAds];
2586             seekNrList[i] = seekNrList[nrOfSeekAds];
2587             ratingList[i] = ratingList[nrOfSeekAds];
2588             colorList[i]  = colorList[nrOfSeekAds];
2589             tcList[i] = tcList[nrOfSeekAds];
2590             xList[i]  = xList[nrOfSeekAds];
2591             yList[i]  = yList[nrOfSeekAds];
2592             zList[i]  = zList[nrOfSeekAds];
2593             seekAdList[nrOfSeekAds] = NULL;
2594             break;
2595         }
2596 }
2597
2598 Boolean
2599 MatchSoughtLine (char *line)
2600 {
2601     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2602     int nr, base, inc, u=0; char dummy;
2603
2604     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2605        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2606        (u=1) &&
2607        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2608         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2609         // match: compact and save the line
2610         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2611         return TRUE;
2612     }
2613     return FALSE;
2614 }
2615
2616 int
2617 DrawSeekGraph ()
2618 {
2619     int i;
2620     if(!seekGraphUp) return FALSE;
2621     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2622     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2623
2624     DrawSeekBackground(0, 0, w, h);
2625     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2626     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2627     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2628         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2629         yy = h-1-yy;
2630         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2631         if(i%500 == 0) {
2632             char buf[MSG_SIZ];
2633             snprintf(buf, MSG_SIZ, "%d", i);
2634             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2635         }
2636     }
2637     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2638     for(i=1; i<100; i+=(i<10?1:5)) {
2639         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2640         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2641         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2642             char buf[MSG_SIZ];
2643             snprintf(buf, MSG_SIZ, "%d", i);
2644             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2645         }
2646     }
2647     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2648     return TRUE;
2649 }
2650
2651 int
2652 SeekGraphClick (ClickType click, int x, int y, int moving)
2653 {
2654     static int lastDown = 0, displayed = 0, lastSecond;
2655     if(y < 0) return FALSE;
2656     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2657         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2658         if(!seekGraphUp) return FALSE;
2659         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2660         DrawPosition(TRUE, NULL);
2661         return TRUE;
2662     }
2663     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2664         if(click == Release || moving) return FALSE;
2665         nrOfSeekAds = 0;
2666         soughtPending = TRUE;
2667         SendToICS(ics_prefix);
2668         SendToICS("sought\n"); // should this be "sought all"?
2669     } else { // issue challenge based on clicked ad
2670         int dist = 10000; int i, closest = 0, second = 0;
2671         for(i=0; i<nrOfSeekAds; i++) {
2672             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2673             if(d < dist) { dist = d; closest = i; }
2674             second += (d - zList[i] < 120); // count in-range ads
2675             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2676         }
2677         if(dist < 120) {
2678             char buf[MSG_SIZ];
2679             second = (second > 1);
2680             if(displayed != closest || second != lastSecond) {
2681                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2682                 lastSecond = second; displayed = closest;
2683             }
2684             if(click == Press) {
2685                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2686                 lastDown = closest;
2687                 return TRUE;
2688             } // on press 'hit', only show info
2689             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2690             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2691             SendToICS(ics_prefix);
2692             SendToICS(buf);
2693             return TRUE; // let incoming board of started game pop down the graph
2694         } else if(click == Release) { // release 'miss' is ignored
2695             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2696             if(moving == 2) { // right up-click
2697                 nrOfSeekAds = 0; // refresh graph
2698                 soughtPending = TRUE;
2699                 SendToICS(ics_prefix);
2700                 SendToICS("sought\n"); // should this be "sought all"?
2701             }
2702             return TRUE;
2703         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2704         // press miss or release hit 'pop down' seek graph
2705         seekGraphUp = FALSE;
2706         DrawPosition(TRUE, NULL);
2707     }
2708     return TRUE;
2709 }
2710
2711 void
2712 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2713 {
2714 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2715 #define STARTED_NONE 0
2716 #define STARTED_MOVES 1
2717 #define STARTED_BOARD 2
2718 #define STARTED_OBSERVE 3
2719 #define STARTED_HOLDINGS 4
2720 #define STARTED_CHATTER 5
2721 #define STARTED_COMMENT 6
2722 #define STARTED_MOVES_NOHIDE 7
2723
2724     static int started = STARTED_NONE;
2725     static char parse[20000];
2726     static int parse_pos = 0;
2727     static char buf[BUF_SIZE + 1];
2728     static int firstTime = TRUE, intfSet = FALSE;
2729     static ColorClass prevColor = ColorNormal;
2730     static int savingComment = FALSE;
2731     static int cmatch = 0; // continuation sequence match
2732     char *bp;
2733     char str[MSG_SIZ];
2734     int i, oldi;
2735     int buf_len;
2736     int next_out;
2737     int tkind;
2738     int backup;    /* [DM] For zippy color lines */
2739     char *p;
2740     char talker[MSG_SIZ]; // [HGM] chat
2741     int channel;
2742
2743     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2744
2745     if (appData.debugMode) {
2746       if (!error) {
2747         fprintf(debugFP, "<ICS: ");
2748         show_bytes(debugFP, data, count);
2749         fprintf(debugFP, "\n");
2750       }
2751     }
2752
2753     if (appData.debugMode) { int f = forwardMostMove;
2754         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2755                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2756                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2757     }
2758     if (count > 0) {
2759         /* If last read ended with a partial line that we couldn't parse,
2760            prepend it to the new read and try again. */
2761         if (leftover_len > 0) {
2762             for (i=0; i<leftover_len; i++)
2763               buf[i] = buf[leftover_start + i];
2764         }
2765
2766     /* copy new characters into the buffer */
2767     bp = buf + leftover_len;
2768     buf_len=leftover_len;
2769     for (i=0; i<count; i++)
2770     {
2771         // ignore these
2772         if (data[i] == '\r')
2773             continue;
2774
2775         // join lines split by ICS?
2776         if (!appData.noJoin)
2777         {
2778             /*
2779                 Joining just consists of finding matches against the
2780                 continuation sequence, and discarding that sequence
2781                 if found instead of copying it.  So, until a match
2782                 fails, there's nothing to do since it might be the
2783                 complete sequence, and thus, something we don't want
2784                 copied.
2785             */
2786             if (data[i] == cont_seq[cmatch])
2787             {
2788                 cmatch++;
2789                 if (cmatch == strlen(cont_seq))
2790                 {
2791                     cmatch = 0; // complete match.  just reset the counter
2792
2793                     /*
2794                         it's possible for the ICS to not include the space
2795                         at the end of the last word, making our [correct]
2796                         join operation fuse two separate words.  the server
2797                         does this when the space occurs at the width setting.
2798                     */
2799                     if (!buf_len || buf[buf_len-1] != ' ')
2800                     {
2801                         *bp++ = ' ';
2802                         buf_len++;
2803                     }
2804                 }
2805                 continue;
2806             }
2807             else if (cmatch)
2808             {
2809                 /*
2810                     match failed, so we have to copy what matched before
2811                     falling through and copying this character.  In reality,
2812                     this will only ever be just the newline character, but
2813                     it doesn't hurt to be precise.
2814                 */
2815                 strncpy(bp, cont_seq, cmatch);
2816                 bp += cmatch;
2817                 buf_len += cmatch;
2818                 cmatch = 0;
2819             }
2820         }
2821
2822         // copy this char
2823         *bp++ = data[i];
2824         buf_len++;
2825     }
2826
2827         buf[buf_len] = NULLCHAR;
2828 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2829         next_out = 0;
2830         leftover_start = 0;
2831
2832         i = 0;
2833         while (i < buf_len) {
2834             /* Deal with part of the TELNET option negotiation
2835                protocol.  We refuse to do anything beyond the
2836                defaults, except that we allow the WILL ECHO option,
2837                which ICS uses to turn off password echoing when we are
2838                directly connected to it.  We reject this option
2839                if localLineEditing mode is on (always on in xboard)
2840                and we are talking to port 23, which might be a real
2841                telnet server that will try to keep WILL ECHO on permanently.
2842              */
2843             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2844                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2845                 unsigned char option;
2846                 oldi = i;
2847                 switch ((unsigned char) buf[++i]) {
2848                   case TN_WILL:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<WILL ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       case TN_ECHO:
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "ECHO ");
2855                         /* Reply only if this is a change, according
2856                            to the protocol rules. */
2857                         if (remoteEchoOption) break;
2858                         if (appData.localLineEditing &&
2859                             atoi(appData.icsPort) == TN_PORT) {
2860                             TelnetRequest(TN_DONT, TN_ECHO);
2861                         } else {
2862                             EchoOff();
2863                             TelnetRequest(TN_DO, TN_ECHO);
2864                             remoteEchoOption = TRUE;
2865                         }
2866                         break;
2867                       default:
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "%d ", option);
2870                         /* Whatever this is, we don't want it. */
2871                         TelnetRequest(TN_DONT, option);
2872                         break;
2873                     }
2874                     break;
2875                   case TN_WONT:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<WONT ");
2878                     switch (option = (unsigned char) buf[++i]) {
2879                       case TN_ECHO:
2880                         if (appData.debugMode)
2881                           fprintf(debugFP, "ECHO ");
2882                         /* Reply only if this is a change, according
2883                            to the protocol rules. */
2884                         if (!remoteEchoOption) break;
2885                         EchoOn();
2886                         TelnetRequest(TN_DONT, TN_ECHO);
2887                         remoteEchoOption = FALSE;
2888                         break;
2889                       default:
2890                         if (appData.debugMode)
2891                           fprintf(debugFP, "%d ", (unsigned char) option);
2892                         /* Whatever this is, it must already be turned
2893                            off, because we never agree to turn on
2894                            anything non-default, so according to the
2895                            protocol rules, we don't reply. */
2896                         break;
2897                     }
2898                     break;
2899                   case TN_DO:
2900                     if (appData.debugMode)
2901                       fprintf(debugFP, "\n<DO ");
2902                     switch (option = (unsigned char) buf[++i]) {
2903                       default:
2904                         /* Whatever this is, we refuse to do it. */
2905                         if (appData.debugMode)
2906                           fprintf(debugFP, "%d ", option);
2907                         TelnetRequest(TN_WONT, option);
2908                         break;
2909                     }
2910                     break;
2911                   case TN_DONT:
2912                     if (appData.debugMode)
2913                       fprintf(debugFP, "\n<DONT ");
2914                     switch (option = (unsigned char) buf[++i]) {
2915                       default:
2916                         if (appData.debugMode)
2917                           fprintf(debugFP, "%d ", option);
2918                         /* Whatever this is, we are already not doing
2919                            it, because we never agree to do anything
2920                            non-default, so according to the protocol
2921                            rules, we don't reply. */
2922                         break;
2923                     }
2924                     break;
2925                   case TN_IAC:
2926                     if (appData.debugMode)
2927                       fprintf(debugFP, "\n<IAC ");
2928                     /* Doubled IAC; pass it through */
2929                     i--;
2930                     break;
2931                   default:
2932                     if (appData.debugMode)
2933                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2934                     /* Drop all other telnet commands on the floor */
2935                     break;
2936                 }
2937                 if (oldi > next_out)
2938                   SendToPlayer(&buf[next_out], oldi - next_out);
2939                 if (++i > next_out)
2940                   next_out = i;
2941                 continue;
2942             }
2943
2944             /* OK, this at least will *usually* work */
2945             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2946                 loggedOn = TRUE;
2947             }
2948
2949             if (loggedOn && !intfSet) {
2950                 if (ics_type == ICS_ICC) {
2951                   snprintf(str, MSG_SIZ,
2952                           "/set-quietly interface %s\n/set-quietly style 12\n",
2953                           programVersion);
2954                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2955                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2956                 } else if (ics_type == ICS_CHESSNET) {
2957                   snprintf(str, MSG_SIZ, "/style 12\n");
2958                 } else {
2959                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2960                   strcat(str, programVersion);
2961                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2962                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2963                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2964 #ifdef WIN32
2965                   strcat(str, "$iset nohighlight 1\n");
2966 #endif
2967                   strcat(str, "$iset lock 1\n$style 12\n");
2968                 }
2969                 SendToICS(str);
2970                 NotifyFrontendLogin();
2971                 intfSet = TRUE;
2972             }
2973
2974             if (started == STARTED_COMMENT) {
2975                 /* Accumulate characters in comment */
2976                 parse[parse_pos++] = buf[i];
2977                 if (buf[i] == '\n') {
2978                     parse[parse_pos] = NULLCHAR;
2979                     if(chattingPartner>=0) {
2980                         char mess[MSG_SIZ];
2981                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2982                         OutputChatMessage(chattingPartner, mess);
2983                         chattingPartner = -1;
2984                         next_out = i+1; // [HGM] suppress printing in ICS window
2985                     } else
2986                     if(!suppressKibitz) // [HGM] kibitz
2987                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2988                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2989                         int nrDigit = 0, nrAlph = 0, j;
2990                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2991                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2992                         parse[parse_pos] = NULLCHAR;
2993                         // try to be smart: if it does not look like search info, it should go to
2994                         // ICS interaction window after all, not to engine-output window.
2995                         for(j=0; j<parse_pos; j++) { // count letters and digits
2996                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2997                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2998                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2999                         }
3000                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3001                             int depth=0; float score;
3002                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3003                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3004                                 pvInfoList[forwardMostMove-1].depth = depth;
3005                                 pvInfoList[forwardMostMove-1].score = 100*score;
3006                             }
3007                             OutputKibitz(suppressKibitz, parse);
3008                         } else {
3009                             char tmp[MSG_SIZ];
3010                             if(gameMode == IcsObserving) // restore original ICS messages
3011                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3012                             else
3013                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3014                             SendToPlayer(tmp, strlen(tmp));
3015                         }
3016                         next_out = i+1; // [HGM] suppress printing in ICS window
3017                     }
3018                     started = STARTED_NONE;
3019                 } else {
3020                     /* Don't match patterns against characters in comment */
3021                     i++;
3022                     continue;
3023                 }
3024             }
3025             if (started == STARTED_CHATTER) {
3026                 if (buf[i] != '\n') {
3027                     /* Don't match patterns against characters in chatter */
3028                     i++;
3029                     continue;
3030                 }
3031                 started = STARTED_NONE;
3032                 if(suppressKibitz) next_out = i+1;
3033             }
3034
3035             /* Kludge to deal with rcmd protocol */
3036             if (firstTime && looking_at(buf, &i, "\001*")) {
3037                 DisplayFatalError(&buf[1], 0, 1);
3038                 continue;
3039             } else {
3040                 firstTime = FALSE;
3041             }
3042
3043             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3044                 ics_type = ICS_ICC;
3045                 ics_prefix = "/";
3046                 if (appData.debugMode)
3047                   fprintf(debugFP, "ics_type %d\n", ics_type);
3048                 continue;
3049             }
3050             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3051                 ics_type = ICS_FICS;
3052                 ics_prefix = "$";
3053                 if (appData.debugMode)
3054                   fprintf(debugFP, "ics_type %d\n", ics_type);
3055                 continue;
3056             }
3057             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3058                 ics_type = ICS_CHESSNET;
3059                 ics_prefix = "/";
3060                 if (appData.debugMode)
3061                   fprintf(debugFP, "ics_type %d\n", ics_type);
3062                 continue;
3063             }
3064
3065             if (!loggedOn &&
3066                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3067                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3068                  looking_at(buf, &i, "will be \"*\""))) {
3069               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3070               continue;
3071             }
3072
3073             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3074               char buf[MSG_SIZ];
3075               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3076               DisplayIcsInteractionTitle(buf);
3077               have_set_title = TRUE;
3078             }
3079
3080             /* skip finger notes */
3081             if (started == STARTED_NONE &&
3082                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3083                  (buf[i] == '1' && buf[i+1] == '0')) &&
3084                 buf[i+2] == ':' && buf[i+3] == ' ') {
3085               started = STARTED_CHATTER;
3086               i += 3;
3087               continue;
3088             }
3089
3090             oldi = i;
3091             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3092             if(appData.seekGraph) {
3093                 if(soughtPending && MatchSoughtLine(buf+i)) {
3094                     i = strstr(buf+i, "rated") - buf;
3095                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096                     next_out = leftover_start = i;
3097                     started = STARTED_CHATTER;
3098                     suppressKibitz = TRUE;
3099                     continue;
3100                 }
3101                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3102                         && looking_at(buf, &i, "* ads displayed")) {
3103                     soughtPending = FALSE;
3104                     seekGraphUp = TRUE;
3105                     DrawSeekGraph();
3106                     continue;
3107                 }
3108                 if(appData.autoRefresh) {
3109                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3110                         int s = (ics_type == ICS_ICC); // ICC format differs
3111                         if(seekGraphUp)
3112                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3113                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3114                         looking_at(buf, &i, "*% "); // eat prompt
3115                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3116                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117                         next_out = i; // suppress
3118                         continue;
3119                     }
3120                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3121                         char *p = star_match[0];
3122                         while(*p) {
3123                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3124                             while(*p && *p++ != ' '); // next
3125                         }
3126                         looking_at(buf, &i, "*% "); // eat prompt
3127                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128                         next_out = i;
3129                         continue;
3130                     }
3131                 }
3132             }
3133
3134             /* skip formula vars */
3135             if (started == STARTED_NONE &&
3136                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3137               started = STARTED_CHATTER;
3138               i += 3;
3139               continue;
3140             }
3141
3142             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3143             if (appData.autoKibitz && started == STARTED_NONE &&
3144                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3145                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3146                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3147                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3148                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3149                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3150                         suppressKibitz = TRUE;
3151                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3152                         next_out = i;
3153                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3154                                 && (gameMode == IcsPlayingWhite)) ||
3155                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3156                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3157                             started = STARTED_CHATTER; // own kibitz we simply discard
3158                         else {
3159                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3160                             parse_pos = 0; parse[0] = NULLCHAR;
3161                             savingComment = TRUE;
3162                             suppressKibitz = gameMode != IcsObserving ? 2 :
3163                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3164                         }
3165                         continue;
3166                 } else
3167                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3168                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3169                          && atoi(star_match[0])) {
3170                     // suppress the acknowledgements of our own autoKibitz
3171                     char *p;
3172                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3173                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3174                     SendToPlayer(star_match[0], strlen(star_match[0]));
3175                     if(looking_at(buf, &i, "*% ")) // eat prompt
3176                         suppressKibitz = FALSE;
3177                     next_out = i;
3178                     continue;
3179                 }
3180             } // [HGM] kibitz: end of patch
3181
3182             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3183
3184             // [HGM] chat: intercept tells by users for which we have an open chat window
3185             channel = -1;
3186             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3187                                            looking_at(buf, &i, "* whispers:") ||
3188                                            looking_at(buf, &i, "* kibitzes:") ||
3189                                            looking_at(buf, &i, "* shouts:") ||
3190                                            looking_at(buf, &i, "* c-shouts:") ||
3191                                            looking_at(buf, &i, "--> * ") ||
3192                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3193                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3194                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3195                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3196                 int p;
3197                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3198                 chattingPartner = -1;
3199
3200                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3201                 for(p=0; p<MAX_CHAT; p++) {
3202                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3203                     talker[0] = '['; strcat(talker, "] ");
3204                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3205                     chattingPartner = p; break;
3206                     }
3207                 } else
3208                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3209                 for(p=0; p<MAX_CHAT; p++) {
3210                     if(!strcmp("kibitzes", chatPartner[p])) {
3211                         talker[0] = '['; strcat(talker, "] ");
3212                         chattingPartner = p; break;
3213                     }
3214                 } else
3215                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3216                 for(p=0; p<MAX_CHAT; p++) {
3217                     if(!strcmp("whispers", chatPartner[p])) {
3218                         talker[0] = '['; strcat(talker, "] ");
3219                         chattingPartner = p; break;
3220                     }
3221                 } else
3222                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3223                   if(buf[i-8] == '-' && buf[i-3] == 't')
3224                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3225                     if(!strcmp("c-shouts", chatPartner[p])) {
3226                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3227                         chattingPartner = p; break;
3228                     }
3229                   }
3230                   if(chattingPartner < 0)
3231                   for(p=0; p<MAX_CHAT; p++) {
3232                     if(!strcmp("shouts", chatPartner[p])) {
3233                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3234                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3235                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3236                         chattingPartner = p; break;
3237                     }
3238                   }
3239                 }
3240                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3241                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3242                     talker[0] = 0; Colorize(ColorTell, FALSE);
3243                     chattingPartner = p; break;
3244                 }
3245                 if(chattingPartner<0) i = oldi; else {
3246                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3247                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3248                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3249                     started = STARTED_COMMENT;
3250                     parse_pos = 0; parse[0] = NULLCHAR;
3251                     savingComment = 3 + chattingPartner; // counts as TRUE
3252                     suppressKibitz = TRUE;
3253                     continue;
3254                 }
3255             } // [HGM] chat: end of patch
3256
3257           backup = i;
3258             if (appData.zippyTalk || appData.zippyPlay) {
3259                 /* [DM] Backup address for color zippy lines */
3260 #if ZIPPY
3261                if (loggedOn == TRUE)
3262                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3263                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3264 #endif
3265             } // [DM] 'else { ' deleted
3266                 if (
3267                     /* Regular tells and says */
3268                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3269                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3270                     looking_at(buf, &i, "* says: ") ||
3271                     /* Don't color "message" or "messages" output */
3272                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3273                     looking_at(buf, &i, "*. * at *:*: ") ||
3274                     looking_at(buf, &i, "--* (*:*): ") ||
3275                     /* Message notifications (same color as tells) */
3276                     looking_at(buf, &i, "* has left a message ") ||
3277                     looking_at(buf, &i, "* just sent you a message:\n") ||
3278                     /* Whispers and kibitzes */
3279                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3280                     looking_at(buf, &i, "* kibitzes: ") ||
3281                     /* Channel tells */
3282                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3283
3284                   if (tkind == 1 && strchr(star_match[0], ':')) {
3285                       /* Avoid "tells you:" spoofs in channels */
3286                      tkind = 3;
3287                   }
3288                   if (star_match[0][0] == NULLCHAR ||
3289                       strchr(star_match[0], ' ') ||
3290                       (tkind == 3 && strchr(star_match[1], ' '))) {
3291                     /* Reject bogus matches */
3292                     i = oldi;
3293                   } else {
3294                     if (appData.colorize) {
3295                       if (oldi > next_out) {
3296                         SendToPlayer(&buf[next_out], oldi - next_out);
3297                         next_out = oldi;
3298                       }
3299                       switch (tkind) {
3300                       case 1:
3301                         Colorize(ColorTell, FALSE);
3302                         curColor = ColorTell;
3303                         break;
3304                       case 2:
3305                         Colorize(ColorKibitz, FALSE);
3306                         curColor = ColorKibitz;
3307                         break;
3308                       case 3:
3309                         p = strrchr(star_match[1], '(');
3310                         if (p == NULL) {
3311                           p = star_match[1];
3312                         } else {
3313                           p++;
3314                         }
3315                         if (atoi(p) == 1) {
3316                           Colorize(ColorChannel1, FALSE);
3317                           curColor = ColorChannel1;
3318                         } else {
3319                           Colorize(ColorChannel, FALSE);
3320                           curColor = ColorChannel;
3321                         }
3322                         break;
3323                       case 5:
3324                         curColor = ColorNormal;
3325                         break;
3326                       }
3327                     }
3328                     if (started == STARTED_NONE && appData.autoComment &&
3329                         (gameMode == IcsObserving ||
3330                          gameMode == IcsPlayingWhite ||
3331                          gameMode == IcsPlayingBlack)) {
3332                       parse_pos = i - oldi;
3333                       memcpy(parse, &buf[oldi], parse_pos);
3334                       parse[parse_pos] = NULLCHAR;
3335                       started = STARTED_COMMENT;
3336                       savingComment = TRUE;
3337                     } else {
3338                       started = STARTED_CHATTER;
3339                       savingComment = FALSE;
3340                     }
3341                     loggedOn = TRUE;
3342                     continue;
3343                   }
3344                 }
3345
3346                 if (looking_at(buf, &i, "* s-shouts: ") ||
3347                     looking_at(buf, &i, "* c-shouts: ")) {
3348                     if (appData.colorize) {
3349                         if (oldi > next_out) {
3350                             SendToPlayer(&buf[next_out], oldi - next_out);
3351                             next_out = oldi;
3352                         }
3353                         Colorize(ColorSShout, FALSE);
3354                         curColor = ColorSShout;
3355                     }
3356                     loggedOn = TRUE;
3357                     started = STARTED_CHATTER;
3358                     continue;
3359                 }
3360
3361                 if (looking_at(buf, &i, "--->")) {
3362                     loggedOn = TRUE;
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* shouts: ") ||
3367                     looking_at(buf, &i, "--> ")) {
3368                     if (appData.colorize) {
3369                         if (oldi > next_out) {
3370                             SendToPlayer(&buf[next_out], oldi - next_out);
3371                             next_out = oldi;
3372                         }
3373                         Colorize(ColorShout, FALSE);
3374                         curColor = ColorShout;
3375                     }
3376                     loggedOn = TRUE;
3377                     started = STARTED_CHATTER;
3378                     continue;
3379                 }
3380
3381                 if (looking_at( buf, &i, "Challenge:")) {
3382                     if (appData.colorize) {
3383                         if (oldi > next_out) {
3384                             SendToPlayer(&buf[next_out], oldi - next_out);
3385                             next_out = oldi;
3386                         }
3387                         Colorize(ColorChallenge, FALSE);
3388                         curColor = ColorChallenge;
3389                     }
3390                     loggedOn = TRUE;
3391                     continue;
3392                 }
3393
3394                 if (looking_at(buf, &i, "* offers you") ||
3395                     looking_at(buf, &i, "* offers to be") ||
3396                     looking_at(buf, &i, "* would like to") ||
3397                     looking_at(buf, &i, "* requests to") ||
3398                     looking_at(buf, &i, "Your opponent offers") ||
3399                     looking_at(buf, &i, "Your opponent requests")) {
3400
3401                     if (appData.colorize) {
3402                         if (oldi > next_out) {
3403                             SendToPlayer(&buf[next_out], oldi - next_out);
3404                             next_out = oldi;
3405                         }
3406                         Colorize(ColorRequest, FALSE);
3407                         curColor = ColorRequest;
3408                     }
3409                     continue;
3410                 }
3411
3412                 if (looking_at(buf, &i, "* (*) seeking")) {
3413                     if (appData.colorize) {
3414                         if (oldi > next_out) {
3415                             SendToPlayer(&buf[next_out], oldi - next_out);
3416                             next_out = oldi;
3417                         }
3418                         Colorize(ColorSeek, FALSE);
3419                         curColor = ColorSeek;
3420                     }
3421                     continue;
3422             }
3423
3424           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3425
3426             if (looking_at(buf, &i, "\\   ")) {
3427                 if (prevColor != ColorNormal) {
3428                     if (oldi > next_out) {
3429                         SendToPlayer(&buf[next_out], oldi - next_out);
3430                         next_out = oldi;
3431                     }
3432                     Colorize(prevColor, TRUE);
3433                     curColor = prevColor;
3434                 }
3435                 if (savingComment) {
3436                     parse_pos = i - oldi;
3437                     memcpy(parse, &buf[oldi], parse_pos);
3438                     parse[parse_pos] = NULLCHAR;
3439                     started = STARTED_COMMENT;
3440                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3441                         chattingPartner = savingComment - 3; // kludge to remember the box
3442                 } else {
3443                     started = STARTED_CHATTER;
3444                 }
3445                 continue;
3446             }
3447
3448             if (looking_at(buf, &i, "Black Strength :") ||
3449                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3450                 looking_at(buf, &i, "<10>") ||
3451                 looking_at(buf, &i, "#@#")) {
3452                 /* Wrong board style */
3453                 loggedOn = TRUE;
3454                 SendToICS(ics_prefix);
3455                 SendToICS("set style 12\n");
3456                 SendToICS(ics_prefix);
3457                 SendToICS("refresh\n");
3458                 continue;
3459             }
3460
3461             if (looking_at(buf, &i, "login:")) {
3462               if (!have_sent_ICS_logon) {
3463                 if(ICSInitScript())
3464                   have_sent_ICS_logon = 1;
3465                 else // no init script was found
3466                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3467               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3468                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3469               }
3470                 continue;
3471             }
3472
3473             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3474                 (looking_at(buf, &i, "\n<12> ") ||
3475                  looking_at(buf, &i, "<12> "))) {
3476                 loggedOn = TRUE;
3477                 if (oldi > next_out) {
3478                     SendToPlayer(&buf[next_out], oldi - next_out);
3479                 }
3480                 next_out = i;
3481                 started = STARTED_BOARD;
3482                 parse_pos = 0;
3483                 continue;
3484             }
3485
3486             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3487                 looking_at(buf, &i, "<b1> ")) {
3488                 if (oldi > next_out) {
3489                     SendToPlayer(&buf[next_out], oldi - next_out);
3490                 }
3491                 next_out = i;
3492                 started = STARTED_HOLDINGS;
3493                 parse_pos = 0;
3494                 continue;
3495             }
3496
3497             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3498                 loggedOn = TRUE;
3499                 /* Header for a move list -- first line */
3500
3501                 switch (ics_getting_history) {
3502                   case H_FALSE:
3503                     switch (gameMode) {
3504                       case IcsIdle:
3505                       case BeginningOfGame:
3506                         /* User typed "moves" or "oldmoves" while we
3507                            were idle.  Pretend we asked for these
3508                            moves and soak them up so user can step
3509                            through them and/or save them.
3510                            */
3511                         Reset(FALSE, TRUE);
3512                         gameMode = IcsObserving;
3513                         ModeHighlight();
3514                         ics_gamenum = -1;
3515                         ics_getting_history = H_GOT_UNREQ_HEADER;
3516                         break;
3517                       case EditGame: /*?*/
3518                       case EditPosition: /*?*/
3519                         /* Should above feature work in these modes too? */
3520                         /* For now it doesn't */
3521                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3522                         break;
3523                       default:
3524                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3525                         break;
3526                     }
3527                     break;
3528                   case H_REQUESTED:
3529                     /* Is this the right one? */
3530                     if (gameInfo.white && gameInfo.black &&
3531                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3532                         strcmp(gameInfo.black, star_match[2]) == 0) {
3533                         /* All is well */
3534                         ics_getting_history = H_GOT_REQ_HEADER;
3535                     }
3536                     break;
3537                   case H_GOT_REQ_HEADER:
3538                   case H_GOT_UNREQ_HEADER:
3539                   case H_GOT_UNWANTED_HEADER:
3540                   case H_GETTING_MOVES:
3541                     /* Should not happen */
3542                     DisplayError(_("Error gathering move list: two headers"), 0);
3543                     ics_getting_history = H_FALSE;
3544                     break;
3545                 }
3546
3547                 /* Save player ratings into gameInfo if needed */
3548                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3549                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3550                     (gameInfo.whiteRating == -1 ||
3551                      gameInfo.blackRating == -1)) {
3552
3553                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3554                     gameInfo.blackRating = string_to_rating(star_match[3]);
3555                     if (appData.debugMode)
3556                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3557                               gameInfo.whiteRating, gameInfo.blackRating);
3558                 }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i,
3563               "* * match, initial time: * minute*, increment: * second")) {
3564                 /* Header for a move list -- second line */
3565                 /* Initial board will follow if this is a wild game */
3566                 if (gameInfo.event != NULL) free(gameInfo.event);
3567                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3568                 gameInfo.event = StrSave(str);
3569                 /* [HGM] we switched variant. Translate boards if needed. */
3570                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3571                 continue;
3572             }
3573
3574             if (looking_at(buf, &i, "Move  ")) {
3575                 /* Beginning of a move list */
3576                 switch (ics_getting_history) {
3577                   case H_FALSE:
3578                     /* Normally should not happen */
3579                     /* Maybe user hit reset while we were parsing */
3580                     break;
3581                   case H_REQUESTED:
3582                     /* Happens if we are ignoring a move list that is not
3583                      * the one we just requested.  Common if the user
3584                      * tries to observe two games without turning off
3585                      * getMoveList */
3586                     break;
3587                   case H_GETTING_MOVES:
3588                     /* Should not happen */
3589                     DisplayError(_("Error gathering move list: nested"), 0);
3590                     ics_getting_history = H_FALSE;
3591                     break;
3592                   case H_GOT_REQ_HEADER:
3593                     ics_getting_history = H_GETTING_MOVES;
3594                     started = STARTED_MOVES;
3595                     parse_pos = 0;
3596                     if (oldi > next_out) {
3597                         SendToPlayer(&buf[next_out], oldi - next_out);
3598                     }
3599                     break;
3600                   case H_GOT_UNREQ_HEADER:
3601                     ics_getting_history = H_GETTING_MOVES;
3602                     started = STARTED_MOVES_NOHIDE;
3603                     parse_pos = 0;
3604                     break;
3605                   case H_GOT_UNWANTED_HEADER:
3606                     ics_getting_history = H_FALSE;
3607                     break;
3608                 }
3609                 continue;
3610             }
3611
3612             if (looking_at(buf, &i, "% ") ||
3613                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3614                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3615                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3616                     soughtPending = FALSE;
3617                     seekGraphUp = TRUE;
3618                     DrawSeekGraph();
3619                 }
3620                 if(suppressKibitz) next_out = i;
3621                 savingComment = FALSE;
3622                 suppressKibitz = 0;
3623                 switch (started) {
3624                   case STARTED_MOVES:
3625                   case STARTED_MOVES_NOHIDE:
3626                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3627                     parse[parse_pos + i - oldi] = NULLCHAR;
3628                     ParseGameHistory(parse);
3629 #if ZIPPY
3630                     if (appData.zippyPlay && first.initDone) {
3631                         FeedMovesToProgram(&first, forwardMostMove);
3632                         if (gameMode == IcsPlayingWhite) {
3633                             if (WhiteOnMove(forwardMostMove)) {
3634                                 if (first.sendTime) {
3635                                   if (first.useColors) {
3636                                     SendToProgram("black\n", &first);
3637                                   }
3638                                   SendTimeRemaining(&first, TRUE);
3639                                 }
3640                                 if (first.useColors) {
3641                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3642                                 }
3643                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3644                                 first.maybeThinking = TRUE;
3645                             } else {
3646                                 if (first.usePlayother) {
3647                                   if (first.sendTime) {
3648                                     SendTimeRemaining(&first, TRUE);
3649                                   }
3650                                   SendToProgram("playother\n", &first);
3651                                   firstMove = FALSE;
3652                                 } else {
3653                                   firstMove = TRUE;
3654                                 }
3655                             }
3656                         } else if (gameMode == IcsPlayingBlack) {
3657                             if (!WhiteOnMove(forwardMostMove)) {
3658                                 if (first.sendTime) {
3659                                   if (first.useColors) {
3660                                     SendToProgram("white\n", &first);
3661                                   }
3662                                   SendTimeRemaining(&first, FALSE);
3663                                 }
3664                                 if (first.useColors) {
3665                                   SendToProgram("black\n", &first);
3666                                 }
3667                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3668                                 first.maybeThinking = TRUE;
3669                             } else {
3670                                 if (first.usePlayother) {
3671                                   if (first.sendTime) {
3672                                     SendTimeRemaining(&first, FALSE);
3673                                   }
3674                                   SendToProgram("playother\n", &first);
3675                                   firstMove = FALSE;
3676                                 } else {
3677                                   firstMove = TRUE;
3678                                 }
3679                             }
3680                         }
3681                     }
3682 #endif
3683                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3684                         /* Moves came from oldmoves or moves command
3685                            while we weren't doing anything else.
3686                            */
3687                         currentMove = forwardMostMove;
3688                         ClearHighlights();/*!!could figure this out*/
3689                         flipView = appData.flipView;
3690                         DrawPosition(TRUE, boards[currentMove]);
3691                         DisplayBothClocks();
3692                         snprintf(str, MSG_SIZ, "%s %s %s",
3693                                 gameInfo.white, _("vs."),  gameInfo.black);
3694                         DisplayTitle(str);
3695                         gameMode = IcsIdle;
3696                     } else {
3697                         /* Moves were history of an active game */
3698                         if (gameInfo.resultDetails != NULL) {
3699                             free(gameInfo.resultDetails);
3700                             gameInfo.resultDetails = NULL;
3701                         }
3702                     }
3703                     HistorySet(parseList, backwardMostMove,
3704                                forwardMostMove, currentMove-1);
3705                     DisplayMove(currentMove - 1);
3706                     if (started == STARTED_MOVES) next_out = i;
3707                     started = STARTED_NONE;
3708                     ics_getting_history = H_FALSE;
3709                     break;
3710
3711                   case STARTED_OBSERVE:
3712                     started = STARTED_NONE;
3713                     SendToICS(ics_prefix);
3714                     SendToICS("refresh\n");
3715                     break;
3716
3717                   default:
3718                     break;
3719                 }
3720                 if(bookHit) { // [HGM] book: simulate book reply
3721                     static char bookMove[MSG_SIZ]; // a bit generous?
3722
3723                     programStats.nodes = programStats.depth = programStats.time =
3724                     programStats.score = programStats.got_only_move = 0;
3725                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3726
3727                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3728                     strcat(bookMove, bookHit);
3729                     HandleMachineMove(bookMove, &first);
3730                 }
3731                 continue;
3732             }
3733
3734             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3735                  started == STARTED_HOLDINGS ||
3736                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3737                 /* Accumulate characters in move list or board */
3738                 parse[parse_pos++] = buf[i];
3739             }
3740
3741             /* Start of game messages.  Mostly we detect start of game
3742                when the first board image arrives.  On some versions
3743                of the ICS, though, we need to do a "refresh" after starting
3744                to observe in order to get the current board right away. */
3745             if (looking_at(buf, &i, "Adding game * to observation list")) {
3746                 started = STARTED_OBSERVE;
3747                 continue;
3748             }
3749
3750             /* Handle auto-observe */
3751             if (appData.autoObserve &&
3752                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3753                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3754                 char *player;
3755                 /* Choose the player that was highlighted, if any. */
3756                 if (star_match[0][0] == '\033' ||
3757                     star_match[1][0] != '\033') {
3758                     player = star_match[0];
3759                 } else {
3760                     player = star_match[2];
3761                 }
3762                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3763                         ics_prefix, StripHighlightAndTitle(player));
3764                 SendToICS(str);
3765
3766                 /* Save ratings from notify string */
3767                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3768                 player1Rating = string_to_rating(star_match[1]);
3769                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3770                 player2Rating = string_to_rating(star_match[3]);
3771
3772                 if (appData.debugMode)
3773                   fprintf(debugFP,
3774                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3775                           player1Name, player1Rating,
3776                           player2Name, player2Rating);
3777
3778                 continue;
3779             }
3780
3781             /* Deal with automatic examine mode after a game,
3782                and with IcsObserving -> IcsExamining transition */
3783             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3784                 looking_at(buf, &i, "has made you an examiner of game *")) {
3785
3786                 int gamenum = atoi(star_match[0]);
3787                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3788                     gamenum == ics_gamenum) {
3789                     /* We were already playing or observing this game;
3790                        no need to refetch history */
3791                     gameMode = IcsExamining;
3792                     if (pausing) {
3793                         pauseExamForwardMostMove = forwardMostMove;
3794                     } else if (currentMove < forwardMostMove) {
3795                         ForwardInner(forwardMostMove);
3796                     }
3797                 } else {
3798                     /* I don't think this case really can happen */
3799                     SendToICS(ics_prefix);
3800                     SendToICS("refresh\n");
3801                 }
3802                 continue;
3803             }
3804
3805             /* Error messages */
3806 //          if (ics_user_moved) {
3807             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3808                 if (looking_at(buf, &i, "Illegal move") ||
3809                     looking_at(buf, &i, "Not a legal move") ||
3810                     looking_at(buf, &i, "Your king is in check") ||
3811                     looking_at(buf, &i, "It isn't your turn") ||
3812                     looking_at(buf, &i, "It is not your move")) {
3813                     /* Illegal move */
3814                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3815                         currentMove = forwardMostMove-1;
3816                         DisplayMove(currentMove - 1); /* before DMError */
3817                         DrawPosition(FALSE, boards[currentMove]);
3818                         SwitchClocks(forwardMostMove-1); // [HGM] race
3819                         DisplayBothClocks();
3820                     }
3821                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3822                     ics_user_moved = 0;
3823                     continue;
3824                 }
3825             }
3826
3827             if (looking_at(buf, &i, "still have time") ||
3828                 looking_at(buf, &i, "not out of time") ||
3829                 looking_at(buf, &i, "either player is out of time") ||
3830                 looking_at(buf, &i, "has timeseal; checking")) {
3831                 /* We must have called his flag a little too soon */
3832                 whiteFlag = blackFlag = FALSE;
3833                 continue;
3834             }
3835
3836             if (looking_at(buf, &i, "added * seconds to") ||
3837                 looking_at(buf, &i, "seconds were added to")) {
3838                 /* Update the clocks */
3839                 SendToICS(ics_prefix);
3840                 SendToICS("refresh\n");
3841                 continue;
3842             }
3843
3844             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3845                 ics_clock_paused = TRUE;
3846                 StopClocks();
3847                 continue;
3848             }
3849
3850             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3851                 ics_clock_paused = FALSE;
3852                 StartClocks();
3853                 continue;
3854             }
3855
3856             /* Grab player ratings from the Creating: message.
3857                Note we have to check for the special case when
3858                the ICS inserts things like [white] or [black]. */
3859             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3860                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3861                 /* star_matches:
3862                    0    player 1 name (not necessarily white)
3863                    1    player 1 rating
3864                    2    empty, white, or black (IGNORED)
3865                    3    player 2 name (not necessarily black)
3866                    4    player 2 rating
3867
3868                    The names/ratings are sorted out when the game
3869                    actually starts (below).
3870                 */
3871                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3872                 player1Rating = string_to_rating(star_match[1]);
3873                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3874                 player2Rating = string_to_rating(star_match[4]);
3875
3876                 if (appData.debugMode)
3877                   fprintf(debugFP,
3878                           "Ratings from 'Creating:' %s %d, %s %d\n",
3879                           player1Name, player1Rating,
3880                           player2Name, player2Rating);
3881
3882                 continue;
3883             }
3884
3885             /* Improved generic start/end-of-game messages */
3886             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3887                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3888                 /* If tkind == 0: */
3889                 /* star_match[0] is the game number */
3890                 /*           [1] is the white player's name */
3891                 /*           [2] is the black player's name */
3892                 /* For end-of-game: */
3893                 /*           [3] is the reason for the game end */
3894                 /*           [4] is a PGN end game-token, preceded by " " */
3895                 /* For start-of-game: */
3896                 /*           [3] begins with "Creating" or "Continuing" */
3897                 /*           [4] is " *" or empty (don't care). */
3898                 int gamenum = atoi(star_match[0]);
3899                 char *whitename, *blackname, *why, *endtoken;
3900                 ChessMove endtype = EndOfFile;
3901
3902                 if (tkind == 0) {
3903                   whitename = star_match[1];
3904                   blackname = star_match[2];
3905                   why = star_match[3];
3906                   endtoken = star_match[4];
3907                 } else {
3908                   whitename = star_match[1];
3909                   blackname = star_match[3];
3910                   why = star_match[5];
3911                   endtoken = star_match[6];
3912                 }
3913
3914                 /* Game start messages */
3915                 if (strncmp(why, "Creating ", 9) == 0 ||
3916                     strncmp(why, "Continuing ", 11) == 0) {
3917                     gs_gamenum = gamenum;
3918                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3919                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3920                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3921 #if ZIPPY
3922                     if (appData.zippyPlay) {
3923                         ZippyGameStart(whitename, blackname);
3924                     }
3925 #endif /*ZIPPY*/
3926                     partnerBoardValid = FALSE; // [HGM] bughouse
3927                     continue;
3928                 }
3929
3930                 /* Game end messages */
3931                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3932                     ics_gamenum != gamenum) {
3933                     continue;
3934                 }
3935                 while (endtoken[0] == ' ') endtoken++;
3936                 switch (endtoken[0]) {
3937                   case '*':
3938                   default:
3939                     endtype = GameUnfinished;
3940                     break;
3941                   case '0':
3942                     endtype = BlackWins;
3943                     break;
3944                   case '1':
3945                     if (endtoken[1] == '/')
3946                       endtype = GameIsDrawn;
3947                     else
3948                       endtype = WhiteWins;
3949                     break;
3950                 }
3951                 GameEnds(endtype, why, GE_ICS);
3952 #if ZIPPY
3953                 if (appData.zippyPlay && first.initDone) {
3954                     ZippyGameEnd(endtype, why);
3955                     if (first.pr == NoProc) {
3956                       /* Start the next process early so that we'll
3957                          be ready for the next challenge */
3958                       StartChessProgram(&first);
3959                     }
3960                     /* Send "new" early, in case this command takes
3961                        a long time to finish, so that we'll be ready
3962                        for the next challenge. */
3963                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3964                     Reset(TRUE, TRUE);
3965                 }
3966 #endif /*ZIPPY*/
3967                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3968                 continue;
3969             }
3970
3971             if (looking_at(buf, &i, "Removing game * from observation") ||
3972                 looking_at(buf, &i, "no longer observing game *") ||
3973                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3974                 if (gameMode == IcsObserving &&
3975                     atoi(star_match[0]) == ics_gamenum)
3976                   {
3977                       /* icsEngineAnalyze */
3978                       if (appData.icsEngineAnalyze) {
3979                             ExitAnalyzeMode();
3980                             ModeHighlight();
3981                       }
3982                       StopClocks();
3983                       gameMode = IcsIdle;
3984                       ics_gamenum = -1;
3985                       ics_user_moved = FALSE;
3986                   }
3987                 continue;
3988             }
3989
3990             if (looking_at(buf, &i, "no longer examining game *")) {
3991                 if (gameMode == IcsExamining &&
3992                     atoi(star_match[0]) == ics_gamenum)
3993                   {
3994                       gameMode = IcsIdle;
3995                       ics_gamenum = -1;
3996                       ics_user_moved = FALSE;
3997                   }
3998                 continue;
3999             }
4000
4001             /* Advance leftover_start past any newlines we find,
4002                so only partial lines can get reparsed */
4003             if (looking_at(buf, &i, "\n")) {
4004                 prevColor = curColor;
4005                 if (curColor != ColorNormal) {
4006                     if (oldi > next_out) {
4007                         SendToPlayer(&buf[next_out], oldi - next_out);
4008                         next_out = oldi;
4009                     }
4010                     Colorize(ColorNormal, FALSE);
4011                     curColor = ColorNormal;
4012                 }
4013                 if (started == STARTED_BOARD) {
4014                     started = STARTED_NONE;
4015                     parse[parse_pos] = NULLCHAR;
4016                     ParseBoard12(parse);
4017                     ics_user_moved = 0;
4018
4019                     /* Send premove here */
4020                     if (appData.premove) {
4021                       char str[MSG_SIZ];
4022                       if (currentMove == 0 &&
4023                           gameMode == IcsPlayingWhite &&
4024                           appData.premoveWhite) {
4025                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4026                         if (appData.debugMode)
4027                           fprintf(debugFP, "Sending premove:\n");
4028                         SendToICS(str);
4029                       } else if (currentMove == 1 &&
4030                                  gameMode == IcsPlayingBlack &&
4031                                  appData.premoveBlack) {
4032                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4033                         if (appData.debugMode)
4034                           fprintf(debugFP, "Sending premove:\n");
4035                         SendToICS(str);
4036                       } else if (gotPremove) {
4037                         gotPremove = 0;
4038                         ClearPremoveHighlights();
4039                         if (appData.debugMode)
4040                           fprintf(debugFP, "Sending premove:\n");
4041                           UserMoveEvent(premoveFromX, premoveFromY,
4042                                         premoveToX, premoveToY,
4043                                         premovePromoChar);
4044                       }
4045                     }
4046
4047                     /* Usually suppress following prompt */
4048                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4049                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4050                         if (looking_at(buf, &i, "*% ")) {
4051                             savingComment = FALSE;
4052                             suppressKibitz = 0;
4053                         }
4054                     }
4055                     next_out = i;
4056                 } else if (started == STARTED_HOLDINGS) {
4057                     int gamenum;
4058                     char new_piece[MSG_SIZ];
4059                     started = STARTED_NONE;
4060                     parse[parse_pos] = NULLCHAR;
4061                     if (appData.debugMode)
4062                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4063                                                         parse, currentMove);
4064                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4065                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4066                         if (gameInfo.variant == VariantNormal) {
4067                           /* [HGM] We seem to switch variant during a game!
4068                            * Presumably no holdings were displayed, so we have
4069                            * to move the position two files to the right to
4070                            * create room for them!
4071                            */
4072                           VariantClass newVariant;
4073                           switch(gameInfo.boardWidth) { // base guess on board width
4074                                 case 9:  newVariant = VariantShogi; break;
4075                                 case 10: newVariant = VariantGreat; break;
4076                                 default: newVariant = VariantCrazyhouse; break;
4077                           }
4078                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4079                           /* Get a move list just to see the header, which
4080                              will tell us whether this is really bug or zh */
4081                           if (ics_getting_history == H_FALSE) {
4082                             ics_getting_history = H_REQUESTED;
4083                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4084                             SendToICS(str);
4085                           }
4086                         }
4087                         new_piece[0] = NULLCHAR;
4088                         sscanf(parse, "game %d white [%s black [%s <- %s",
4089                                &gamenum, white_holding, black_holding,
4090                                new_piece);
4091                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4092                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4093                         /* [HGM] copy holdings to board holdings area */
4094                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4095                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4096                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4097 #if ZIPPY
4098                         if (appData.zippyPlay && first.initDone) {
4099                             ZippyHoldings(white_holding, black_holding,
4100                                           new_piece);
4101                         }
4102 #endif /*ZIPPY*/
4103                         if (tinyLayout || smallLayout) {
4104                             char wh[16], bh[16];
4105                             PackHolding(wh, white_holding);
4106                             PackHolding(bh, black_holding);
4107                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4108                                     gameInfo.white, gameInfo.black);
4109                         } else {
4110                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4111                                     gameInfo.white, white_holding, _("vs."),
4112                                     gameInfo.black, black_holding);
4113                         }
4114                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4115                         DrawPosition(FALSE, boards[currentMove]);
4116                         DisplayTitle(str);
4117                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4118                         sscanf(parse, "game %d white [%s black [%s <- %s",
4119                                &gamenum, white_holding, black_holding,
4120                                new_piece);
4121                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4122                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4123                         /* [HGM] copy holdings to partner-board holdings area */
4124                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4125                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4126                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4127                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4128                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4129                       }
4130                     }
4131                     /* Suppress following prompt */
4132                     if (looking_at(buf, &i, "*% ")) {
4133                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4134                         savingComment = FALSE;
4135                         suppressKibitz = 0;
4136                     }
4137                     next_out = i;
4138                 }
4139                 continue;
4140             }
4141
4142             i++;                /* skip unparsed character and loop back */
4143         }
4144
4145         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4146 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4147 //          SendToPlayer(&buf[next_out], i - next_out);
4148             started != STARTED_HOLDINGS && leftover_start > next_out) {
4149             SendToPlayer(&buf[next_out], leftover_start - next_out);
4150             next_out = i;
4151         }
4152
4153         leftover_len = buf_len - leftover_start;
4154         /* if buffer ends with something we couldn't parse,
4155            reparse it after appending the next read */
4156
4157     } else if (count == 0) {
4158         RemoveInputSource(isr);
4159         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4160     } else {
4161         DisplayFatalError(_("Error reading from ICS"), error, 1);
4162     }
4163 }
4164
4165
4166 /* Board style 12 looks like this:
4167
4168    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4169
4170  * The "<12> " is stripped before it gets to this routine.  The two
4171  * trailing 0's (flip state and clock ticking) are later addition, and
4172  * some chess servers may not have them, or may have only the first.
4173  * Additional trailing fields may be added in the future.
4174  */
4175
4176 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4177
4178 #define RELATION_OBSERVING_PLAYED    0
4179 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4180 #define RELATION_PLAYING_MYMOVE      1
4181 #define RELATION_PLAYING_NOTMYMOVE  -1
4182 #define RELATION_EXAMINING           2
4183 #define RELATION_ISOLATED_BOARD     -3
4184 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4185
4186 void
4187 ParseBoard12 (char *string)
4188 {
4189 #if ZIPPY
4190     int i, takeback;
4191     char *bookHit = NULL; // [HGM] book
4192 #endif
4193     GameMode newGameMode;
4194     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4195     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4196     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4197     char to_play, board_chars[200];
4198     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4199     char black[32], white[32];
4200     Board board;
4201     int prevMove = currentMove;
4202     int ticking = 2;
4203     ChessMove moveType;
4204     int fromX, fromY, toX, toY;
4205     char promoChar;
4206     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4207     Boolean weird = FALSE, reqFlag = FALSE;
4208
4209     fromX = fromY = toX = toY = -1;
4210
4211     newGame = FALSE;
4212
4213     if (appData.debugMode)
4214       fprintf(debugFP, _("Parsing board: %s\n"), string);
4215
4216     move_str[0] = NULLCHAR;
4217     elapsed_time[0] = NULLCHAR;
4218     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4219         int  i = 0, j;
4220         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4221             if(string[i] == ' ') { ranks++; files = 0; }
4222             else files++;
4223             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4224             i++;
4225         }
4226         for(j = 0; j <i; j++) board_chars[j] = string[j];
4227         board_chars[i] = '\0';
4228         string += i + 1;
4229     }
4230     n = sscanf(string, PATTERN, &to_play, &double_push,
4231                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4232                &gamenum, white, black, &relation, &basetime, &increment,
4233                &white_stren, &black_stren, &white_time, &black_time,
4234                &moveNum, str, elapsed_time, move_str, &ics_flip,
4235                &ticking);
4236
4237     if (n < 21) {
4238         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4239         DisplayError(str, 0);
4240         return;
4241     }
4242
4243     /* Convert the move number to internal form */
4244     moveNum = (moveNum - 1) * 2;
4245     if (to_play == 'B') moveNum++;
4246     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4247       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4248                         0, 1);
4249       return;
4250     }
4251
4252     switch (relation) {
4253       case RELATION_OBSERVING_PLAYED:
4254       case RELATION_OBSERVING_STATIC:
4255         if (gamenum == -1) {
4256             /* Old ICC buglet */
4257             relation = RELATION_OBSERVING_STATIC;
4258         }
4259         newGameMode = IcsObserving;
4260         break;
4261       case RELATION_PLAYING_MYMOVE:
4262       case RELATION_PLAYING_NOTMYMOVE:
4263         newGameMode =
4264           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4265             IcsPlayingWhite : IcsPlayingBlack;
4266         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4267         break;
4268       case RELATION_EXAMINING:
4269         newGameMode = IcsExamining;
4270         break;
4271       case RELATION_ISOLATED_BOARD:
4272       default:
4273         /* Just display this board.  If user was doing something else,
4274            we will forget about it until the next board comes. */
4275         newGameMode = IcsIdle;
4276         break;
4277       case RELATION_STARTING_POSITION:
4278         newGameMode = gameMode;
4279         break;
4280     }
4281
4282     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4283         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4284          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4285       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4286       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4287       static int lastBgGame = -1;
4288       char *toSqr;
4289       for (k = 0; k < ranks; k++) {
4290         for (j = 0; j < files; j++)
4291           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4292         if(gameInfo.holdingsWidth > 1) {
4293              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4294              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4295         }
4296       }
4297       CopyBoard(partnerBoard, board);
4298       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4299         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4300         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4301       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4302       if(toSqr = strchr(str, '-')) {
4303         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4304         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4305       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4306       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4307       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4308       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4309       if(twoBoards) {
4310           DisplayWhiteClock(white_time*fac, to_play == 'W');
4311           DisplayBlackClock(black_time*fac, to_play != 'W');
4312           activePartner = to_play;
4313           if(gamenum != lastBgGame) {
4314               char buf[MSG_SIZ];
4315               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4316               DisplayTitle(buf);
4317           }
4318           lastBgGame = gamenum;
4319           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4320                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4321       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4322                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4323       DisplayMessage(partnerStatus, "");
4324         partnerBoardValid = TRUE;
4325       return;
4326     }
4327
4328     if(appData.dualBoard && appData.bgObserve) {
4329         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4330             SendToICS(ics_prefix), SendToICS("pobserve\n");
4331         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4332             char buf[MSG_SIZ];
4333             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4334             SendToICS(buf);
4335         }
4336     }
4337
4338     /* Modify behavior for initial board display on move listing
4339        of wild games.
4340        */
4341     switch (ics_getting_history) {
4342       case H_FALSE:
4343       case H_REQUESTED:
4344         break;
4345       case H_GOT_REQ_HEADER:
4346       case H_GOT_UNREQ_HEADER:
4347         /* This is the initial position of the current game */
4348         gamenum = ics_gamenum;
4349         moveNum = 0;            /* old ICS bug workaround */
4350         if (to_play == 'B') {
4351           startedFromSetupPosition = TRUE;
4352           blackPlaysFirst = TRUE;
4353           moveNum = 1;
4354           if (forwardMostMove == 0) forwardMostMove = 1;
4355           if (backwardMostMove == 0) backwardMostMove = 1;
4356           if (currentMove == 0) currentMove = 1;
4357         }
4358         newGameMode = gameMode;
4359         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4360         break;
4361       case H_GOT_UNWANTED_HEADER:
4362         /* This is an initial board that we don't want */
4363         return;
4364       case H_GETTING_MOVES:
4365         /* Should not happen */
4366         DisplayError(_("Error gathering move list: extra board"), 0);
4367         ics_getting_history = H_FALSE;
4368         return;
4369     }
4370
4371    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4372                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4373                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4374      /* [HGM] We seem to have switched variant unexpectedly
4375       * Try to guess new variant from board size
4376       */
4377           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4378           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4379           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4380           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4381           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4382           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4383           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4384           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4385           /* Get a move list just to see the header, which
4386              will tell us whether this is really bug or zh */
4387           if (ics_getting_history == H_FALSE) {
4388             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4389             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4390             SendToICS(str);
4391           }
4392     }
4393
4394     /* Take action if this is the first board of a new game, or of a
4395        different game than is currently being displayed.  */
4396     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4397         relation == RELATION_ISOLATED_BOARD) {
4398
4399         /* Forget the old game and get the history (if any) of the new one */
4400         if (gameMode != BeginningOfGame) {
4401           Reset(TRUE, TRUE);
4402         }
4403         newGame = TRUE;
4404         if (appData.autoRaiseBoard) BoardToTop();
4405         prevMove = -3;
4406         if (gamenum == -1) {
4407             newGameMode = IcsIdle;
4408         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4409                    appData.getMoveList && !reqFlag) {
4410             /* Need to get game history */
4411             ics_getting_history = H_REQUESTED;
4412             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4413             SendToICS(str);
4414         }
4415
4416         /* Initially flip the board to have black on the bottom if playing
4417            black or if the ICS flip flag is set, but let the user change
4418            it with the Flip View button. */
4419         flipView = appData.autoFlipView ?
4420           (newGameMode == IcsPlayingBlack) || ics_flip :
4421           appData.flipView;
4422
4423         /* Done with values from previous mode; copy in new ones */
4424         gameMode = newGameMode;
4425         ModeHighlight();
4426         ics_gamenum = gamenum;
4427         if (gamenum == gs_gamenum) {
4428             int klen = strlen(gs_kind);
4429             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4430             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4431             gameInfo.event = StrSave(str);
4432         } else {
4433             gameInfo.event = StrSave("ICS game");
4434         }
4435         gameInfo.site = StrSave(appData.icsHost);
4436         gameInfo.date = PGNDate();
4437         gameInfo.round = StrSave("-");
4438         gameInfo.white = StrSave(white);
4439         gameInfo.black = StrSave(black);
4440         timeControl = basetime * 60 * 1000;
4441         timeControl_2 = 0;
4442         timeIncrement = increment * 1000;
4443         movesPerSession = 0;
4444         gameInfo.timeControl = TimeControlTagValue();
4445         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4446   if (appData.debugMode) {
4447     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4448     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4449     setbuf(debugFP, NULL);
4450   }
4451
4452         gameInfo.outOfBook = NULL;
4453
4454         /* Do we have the ratings? */
4455         if (strcmp(player1Name, white) == 0 &&
4456             strcmp(player2Name, black) == 0) {
4457             if (appData.debugMode)
4458               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4459                       player1Rating, player2Rating);
4460             gameInfo.whiteRating = player1Rating;
4461             gameInfo.blackRating = player2Rating;
4462         } else if (strcmp(player2Name, white) == 0 &&
4463                    strcmp(player1Name, black) == 0) {
4464             if (appData.debugMode)
4465               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4466                       player2Rating, player1Rating);
4467             gameInfo.whiteRating = player2Rating;
4468             gameInfo.blackRating = player1Rating;
4469         }
4470         player1Name[0] = player2Name[0] = NULLCHAR;
4471
4472         /* Silence shouts if requested */
4473         if (appData.quietPlay &&
4474             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4475             SendToICS(ics_prefix);
4476             SendToICS("set shout 0\n");
4477         }
4478     }
4479
4480     /* Deal with midgame name changes */
4481     if (!newGame) {
4482         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4483             if (gameInfo.white) free(gameInfo.white);
4484             gameInfo.white = StrSave(white);
4485         }
4486         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4487             if (gameInfo.black) free(gameInfo.black);
4488             gameInfo.black = StrSave(black);
4489         }
4490     }
4491
4492     /* Throw away game result if anything actually changes in examine mode */
4493     if (gameMode == IcsExamining && !newGame) {
4494         gameInfo.result = GameUnfinished;
4495         if (gameInfo.resultDetails != NULL) {
4496             free(gameInfo.resultDetails);
4497             gameInfo.resultDetails = NULL;
4498         }
4499     }
4500
4501     /* In pausing && IcsExamining mode, we ignore boards coming
4502        in if they are in a different variation than we are. */
4503     if (pauseExamInvalid) return;
4504     if (pausing && gameMode == IcsExamining) {
4505         if (moveNum <= pauseExamForwardMostMove) {
4506             pauseExamInvalid = TRUE;
4507             forwardMostMove = pauseExamForwardMostMove;
4508             return;
4509         }
4510     }
4511
4512   if (appData.debugMode) {
4513     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4514   }
4515     /* Parse the board */
4516     for (k = 0; k < ranks; k++) {
4517       for (j = 0; j < files; j++)
4518         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4519       if(gameInfo.holdingsWidth > 1) {
4520            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4521            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4522       }
4523     }
4524     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4525       board[5][BOARD_RGHT+1] = WhiteAngel;
4526       board[6][BOARD_RGHT+1] = WhiteMarshall;
4527       board[1][0] = BlackMarshall;
4528       board[2][0] = BlackAngel;
4529       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4530     }
4531     CopyBoard(boards[moveNum], board);
4532     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4533     if (moveNum == 0) {
4534         startedFromSetupPosition =
4535           !CompareBoards(board, initialPosition);
4536         if(startedFromSetupPosition)
4537             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4538     }
4539
4540     /* [HGM] Set castling rights. Take the outermost Rooks,
4541        to make it also work for FRC opening positions. Note that board12
4542        is really defective for later FRC positions, as it has no way to
4543        indicate which Rook can castle if they are on the same side of King.
4544        For the initial position we grant rights to the outermost Rooks,
4545        and remember thos rights, and we then copy them on positions
4546        later in an FRC game. This means WB might not recognize castlings with
4547        Rooks that have moved back to their original position as illegal,
4548        but in ICS mode that is not its job anyway.
4549     */
4550     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4551     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4552
4553         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4554             if(board[0][i] == WhiteRook) j = i;
4555         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4556         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4557             if(board[0][i] == WhiteRook) j = i;
4558         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4559         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4560             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4561         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4562         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4563             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4564         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4565
4566         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4567         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4568         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4569             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4570         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4571             if(board[BOARD_HEIGHT-1][k] == bKing)
4572                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4573         if(gameInfo.variant == VariantTwoKings) {
4574             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4575             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4576             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4577         }
4578     } else { int r;
4579         r = boards[moveNum][CASTLING][0] = initialRights[0];
4580         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4581         r = boards[moveNum][CASTLING][1] = initialRights[1];
4582         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4583         r = boards[moveNum][CASTLING][3] = initialRights[3];
4584         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4585         r = boards[moveNum][CASTLING][4] = initialRights[4];
4586         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4587         /* wildcastle kludge: always assume King has rights */
4588         r = boards[moveNum][CASTLING][2] = initialRights[2];
4589         r = boards[moveNum][CASTLING][5] = initialRights[5];
4590     }
4591     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4592     boards[moveNum][EP_STATUS] = EP_NONE;
4593     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4594     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4595     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4596
4597
4598     if (ics_getting_history == H_GOT_REQ_HEADER ||
4599         ics_getting_history == H_GOT_UNREQ_HEADER) {
4600         /* This was an initial position from a move list, not
4601            the current position */
4602         return;
4603     }
4604
4605     /* Update currentMove and known move number limits */
4606     newMove = newGame || moveNum > forwardMostMove;
4607
4608     if (newGame) {
4609         forwardMostMove = backwardMostMove = currentMove = moveNum;
4610         if (gameMode == IcsExamining && moveNum == 0) {
4611           /* Workaround for ICS limitation: we are not told the wild
4612              type when starting to examine a game.  But if we ask for
4613              the move list, the move list header will tell us */
4614             ics_getting_history = H_REQUESTED;
4615             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4616             SendToICS(str);
4617         }
4618     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4619                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4620 #if ZIPPY
4621         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4622         /* [HGM] applied this also to an engine that is silently watching        */
4623         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4624             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4625             gameInfo.variant == currentlyInitializedVariant) {
4626           takeback = forwardMostMove - moveNum;
4627           for (i = 0; i < takeback; i++) {
4628             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4629             SendToProgram("undo\n", &first);
4630           }
4631         }
4632 #endif
4633
4634         forwardMostMove = moveNum;
4635         if (!pausing || currentMove > forwardMostMove)
4636           currentMove = forwardMostMove;
4637     } else {
4638         /* New part of history that is not contiguous with old part */
4639         if (pausing && gameMode == IcsExamining) {
4640             pauseExamInvalid = TRUE;
4641             forwardMostMove = pauseExamForwardMostMove;
4642             return;
4643         }
4644         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4645 #if ZIPPY
4646             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4647                 // [HGM] when we will receive the move list we now request, it will be
4648                 // fed to the engine from the first move on. So if the engine is not
4649                 // in the initial position now, bring it there.
4650                 InitChessProgram(&first, 0);
4651             }
4652 #endif
4653             ics_getting_history = H_REQUESTED;
4654             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4655             SendToICS(str);
4656         }
4657         forwardMostMove = backwardMostMove = currentMove = moveNum;
4658     }
4659
4660     /* Update the clocks */
4661     if (strchr(elapsed_time, '.')) {
4662       /* Time is in ms */
4663       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4664       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4665     } else {
4666       /* Time is in seconds */
4667       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4668       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4669     }
4670
4671
4672 #if ZIPPY
4673     if (appData.zippyPlay && newGame &&
4674         gameMode != IcsObserving && gameMode != IcsIdle &&
4675         gameMode != IcsExamining)
4676       ZippyFirstBoard(moveNum, basetime, increment);
4677 #endif
4678
4679     /* Put the move on the move list, first converting
4680        to canonical algebraic form. */
4681     if (moveNum > 0) {
4682   if (appData.debugMode) {
4683     if (appData.debugMode) { int f = forwardMostMove;
4684         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4685                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4686                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4687     }
4688     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4689     fprintf(debugFP, "moveNum = %d\n", moveNum);
4690     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4691     setbuf(debugFP, NULL);
4692   }
4693         if (moveNum <= backwardMostMove) {
4694             /* We don't know what the board looked like before
4695                this move.  Punt. */
4696           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4697             strcat(parseList[moveNum - 1], " ");
4698             strcat(parseList[moveNum - 1], elapsed_time);
4699             moveList[moveNum - 1][0] = NULLCHAR;
4700         } else if (strcmp(move_str, "none") == 0) {
4701             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4702             /* Again, we don't know what the board looked like;
4703                this is really the start of the game. */
4704             parseList[moveNum - 1][0] = NULLCHAR;
4705             moveList[moveNum - 1][0] = NULLCHAR;
4706             backwardMostMove = moveNum;
4707             startedFromSetupPosition = TRUE;
4708             fromX = fromY = toX = toY = -1;
4709         } else {
4710           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4711           //                 So we parse the long-algebraic move string in stead of the SAN move
4712           int valid; char buf[MSG_SIZ], *prom;
4713
4714           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4715                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4716           // str looks something like "Q/a1-a2"; kill the slash
4717           if(str[1] == '/')
4718             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4719           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4720           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4721                 strcat(buf, prom); // long move lacks promo specification!
4722           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4723                 if(appData.debugMode)
4724                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4725                 safeStrCpy(move_str, buf, MSG_SIZ);
4726           }
4727           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4728                                 &fromX, &fromY, &toX, &toY, &promoChar)
4729                || ParseOneMove(buf, moveNum - 1, &moveType,
4730                                 &fromX, &fromY, &toX, &toY, &promoChar);
4731           // end of long SAN patch
4732           if (valid) {
4733             (void) CoordsToAlgebraic(boards[moveNum - 1],
4734                                      PosFlags(moveNum - 1),
4735                                      fromY, fromX, toY, toX, promoChar,
4736                                      parseList[moveNum-1]);
4737             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4738               case MT_NONE:
4739               case MT_STALEMATE:
4740               default:
4741                 break;
4742               case MT_CHECK:
4743                 if(gameInfo.variant != VariantShogi)
4744                     strcat(parseList[moveNum - 1], "+");
4745                 break;
4746               case MT_CHECKMATE:
4747               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4748                 strcat(parseList[moveNum - 1], "#");
4749                 break;
4750             }
4751             strcat(parseList[moveNum - 1], " ");
4752             strcat(parseList[moveNum - 1], elapsed_time);
4753             /* currentMoveString is set as a side-effect of ParseOneMove */
4754             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4755             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4756             strcat(moveList[moveNum - 1], "\n");
4757
4758             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4759                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4760               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4761                 ChessSquare old, new = boards[moveNum][k][j];
4762                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4763                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4764                   if(old == new) continue;
4765                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4766                   else if(new == WhiteWazir || new == BlackWazir) {
4767                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4768                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4769                       else boards[moveNum][k][j] = old; // preserve type of Gold
4770                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4771                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4772               }
4773           } else {
4774             /* Move from ICS was illegal!?  Punt. */
4775             if (appData.debugMode) {
4776               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4777               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4778             }
4779             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4780             strcat(parseList[moveNum - 1], " ");
4781             strcat(parseList[moveNum - 1], elapsed_time);
4782             moveList[moveNum - 1][0] = NULLCHAR;
4783             fromX = fromY = toX = toY = -1;
4784           }
4785         }
4786   if (appData.debugMode) {
4787     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4788     setbuf(debugFP, NULL);
4789   }
4790
4791 #if ZIPPY
4792         /* Send move to chess program (BEFORE animating it). */
4793         if (appData.zippyPlay && !newGame && newMove &&
4794            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4795
4796             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4797                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4798                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4799                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4800                             move_str);
4801                     DisplayError(str, 0);
4802                 } else {
4803                     if (first.sendTime) {
4804                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4805                     }
4806                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4807                     if (firstMove && !bookHit) {
4808                         firstMove = FALSE;
4809                         if (first.useColors) {
4810                           SendToProgram(gameMode == IcsPlayingWhite ?
4811                                         "white\ngo\n" :
4812                                         "black\ngo\n", &first);
4813                         } else {
4814                           SendToProgram("go\n", &first);
4815                         }
4816                         first.maybeThinking = TRUE;
4817                     }
4818                 }
4819             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4820               if (moveList[moveNum - 1][0] == NULLCHAR) {
4821                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4822                 DisplayError(str, 0);
4823               } else {
4824                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4825                 SendMoveToProgram(moveNum - 1, &first);
4826               }
4827             }
4828         }
4829 #endif
4830     }
4831
4832     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4833         /* If move comes from a remote source, animate it.  If it
4834            isn't remote, it will have already been animated. */
4835         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4836             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4837         }
4838         if (!pausing && appData.highlightLastMove) {
4839             SetHighlights(fromX, fromY, toX, toY);
4840         }
4841     }
4842
4843     /* Start the clocks */
4844     whiteFlag = blackFlag = FALSE;
4845     appData.clockMode = !(basetime == 0 && increment == 0);
4846     if (ticking == 0) {
4847       ics_clock_paused = TRUE;
4848       StopClocks();
4849     } else if (ticking == 1) {
4850       ics_clock_paused = FALSE;
4851     }
4852     if (gameMode == IcsIdle ||
4853         relation == RELATION_OBSERVING_STATIC ||
4854         relation == RELATION_EXAMINING ||
4855         ics_clock_paused)
4856       DisplayBothClocks();
4857     else
4858       StartClocks();
4859
4860     /* Display opponents and material strengths */
4861     if (gameInfo.variant != VariantBughouse &&
4862         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4863         if (tinyLayout || smallLayout) {
4864             if(gameInfo.variant == VariantNormal)
4865               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4866                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4867                     basetime, increment);
4868             else
4869               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4871                     basetime, increment, (int) gameInfo.variant);
4872         } else {
4873             if(gameInfo.variant == VariantNormal)
4874               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4875                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4876                     basetime, increment);
4877             else
4878               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4879                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4880                     basetime, increment, VariantName(gameInfo.variant));
4881         }
4882         DisplayTitle(str);
4883   if (appData.debugMode) {
4884     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4885   }
4886     }
4887
4888
4889     /* Display the board */
4890     if (!pausing && !appData.noGUI) {
4891
4892       if (appData.premove)
4893           if (!gotPremove ||
4894              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4895              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4896               ClearPremoveHighlights();
4897
4898       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4899         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4900       DrawPosition(j, boards[currentMove]);
4901
4902       DisplayMove(moveNum - 1);
4903       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4904             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4905               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4906         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4907       }
4908     }
4909
4910     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4911 #if ZIPPY
4912     if(bookHit) { // [HGM] book: simulate book reply
4913         static char bookMove[MSG_SIZ]; // a bit generous?
4914
4915         programStats.nodes = programStats.depth = programStats.time =
4916         programStats.score = programStats.got_only_move = 0;
4917         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4918
4919         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4920         strcat(bookMove, bookHit);
4921         HandleMachineMove(bookMove, &first);
4922     }
4923 #endif
4924 }
4925
4926 void
4927 GetMoveListEvent ()
4928 {
4929     char buf[MSG_SIZ];
4930     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4931         ics_getting_history = H_REQUESTED;
4932         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4933         SendToICS(buf);
4934     }
4935 }
4936
4937 void
4938 SendToBoth (char *msg)
4939 {   // to make it easy to keep two engines in step in dual analysis
4940     SendToProgram(msg, &first);
4941     if(second.analyzing) SendToProgram(msg, &second);
4942 }
4943
4944 void
4945 AnalysisPeriodicEvent (int force)
4946 {
4947     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4948          && !force) || !appData.periodicUpdates)
4949       return;
4950
4951     /* Send . command to Crafty to collect stats */
4952     SendToBoth(".\n");
4953
4954     /* Don't send another until we get a response (this makes
4955        us stop sending to old Crafty's which don't understand
4956        the "." command (sending illegal cmds resets node count & time,
4957        which looks bad)) */
4958     programStats.ok_to_send = 0;
4959 }
4960
4961 void
4962 ics_update_width (int new_width)
4963 {
4964         ics_printf("set width %d\n", new_width);
4965 }
4966
4967 void
4968 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4969 {
4970     char buf[MSG_SIZ];
4971
4972     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4973         // null move in variant where engine does not understand it (for analysis purposes)
4974         SendBoard(cps, moveNum + 1); // send position after move in stead.
4975         return;
4976     }
4977     if (cps->useUsermove) {
4978       SendToProgram("usermove ", cps);
4979     }
4980     if (cps->useSAN) {
4981       char *space;
4982       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4983         int len = space - parseList[moveNum];
4984         memcpy(buf, parseList[moveNum], len);
4985         buf[len++] = '\n';
4986         buf[len] = NULLCHAR;
4987       } else {
4988         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4989       }
4990       SendToProgram(buf, cps);
4991     } else {
4992       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4993         AlphaRank(moveList[moveNum], 4);
4994         SendToProgram(moveList[moveNum], cps);
4995         AlphaRank(moveList[moveNum], 4); // and back
4996       } else
4997       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4998        * the engine. It would be nice to have a better way to identify castle
4999        * moves here. */
5000       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5001                                                                          && cps->useOOCastle) {
5002         int fromX = moveList[moveNum][0] - AAA;
5003         int fromY = moveList[moveNum][1] - ONE;
5004         int toX = moveList[moveNum][2] - AAA;
5005         int toY = moveList[moveNum][3] - ONE;
5006         if((boards[moveNum][fromY][fromX] == WhiteKing
5007             && boards[moveNum][toY][toX] == WhiteRook)
5008            || (boards[moveNum][fromY][fromX] == BlackKing
5009                && boards[moveNum][toY][toX] == BlackRook)) {
5010           if(toX > fromX) SendToProgram("O-O\n", cps);
5011           else SendToProgram("O-O-O\n", cps);
5012         }
5013         else SendToProgram(moveList[moveNum], cps);
5014       } else
5015       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5016         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5017           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5018           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5019                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5020         } else
5021           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5022                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5023         SendToProgram(buf, cps);
5024       }
5025       else SendToProgram(moveList[moveNum], cps);
5026       /* End of additions by Tord */
5027     }
5028
5029     /* [HGM] setting up the opening has brought engine in force mode! */
5030     /*       Send 'go' if we are in a mode where machine should play. */
5031     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5032         (gameMode == TwoMachinesPlay   ||
5033 #if ZIPPY
5034          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5035 #endif
5036          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5037         SendToProgram("go\n", cps);
5038   if (appData.debugMode) {
5039     fprintf(debugFP, "(extra)\n");
5040   }
5041     }
5042     setboardSpoiledMachineBlack = 0;
5043 }
5044
5045 void
5046 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5047 {
5048     char user_move[MSG_SIZ];
5049     char suffix[4];
5050
5051     if(gameInfo.variant == VariantSChess && promoChar) {
5052         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5053         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5054     } else suffix[0] = NULLCHAR;
5055
5056     switch (moveType) {
5057       default:
5058         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5059                 (int)moveType, fromX, fromY, toX, toY);
5060         DisplayError(user_move + strlen("say "), 0);
5061         break;
5062       case WhiteKingSideCastle:
5063       case BlackKingSideCastle:
5064       case WhiteQueenSideCastleWild:
5065       case BlackQueenSideCastleWild:
5066       /* PUSH Fabien */
5067       case WhiteHSideCastleFR:
5068       case BlackHSideCastleFR:
5069       /* POP Fabien */
5070         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5071         break;
5072       case WhiteQueenSideCastle:
5073       case BlackQueenSideCastle:
5074       case WhiteKingSideCastleWild:
5075       case BlackKingSideCastleWild:
5076       /* PUSH Fabien */
5077       case WhiteASideCastleFR:
5078       case BlackASideCastleFR:
5079       /* POP Fabien */
5080         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5081         break;
5082       case WhiteNonPromotion:
5083       case BlackNonPromotion:
5084         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5085         break;
5086       case WhitePromotion:
5087       case BlackPromotion:
5088         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5089           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5090                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5091                 PieceToChar(WhiteFerz));
5092         else if(gameInfo.variant == VariantGreat)
5093           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5094                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5095                 PieceToChar(WhiteMan));
5096         else
5097           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5098                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5099                 promoChar);
5100         break;
5101       case WhiteDrop:
5102       case BlackDrop:
5103       drop:
5104         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5105                  ToUpper(PieceToChar((ChessSquare) fromX)),
5106                  AAA + toX, ONE + toY);
5107         break;
5108       case IllegalMove:  /* could be a variant we don't quite understand */
5109         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5110       case NormalMove:
5111       case WhiteCapturesEnPassant:
5112       case BlackCapturesEnPassant:
5113         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5114                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5115         break;
5116     }
5117     SendToICS(user_move);
5118     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5119         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5120 }
5121
5122 void
5123 UploadGameEvent ()
5124 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5125     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5126     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5127     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5128       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5129       return;
5130     }
5131     if(gameMode != IcsExamining) { // is this ever not the case?
5132         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5133
5134         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5135           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5136         } else { // on FICS we must first go to general examine mode
5137           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5138         }
5139         if(gameInfo.variant != VariantNormal) {
5140             // try figure out wild number, as xboard names are not always valid on ICS
5141             for(i=1; i<=36; i++) {
5142               snprintf(buf, MSG_SIZ, "wild/%d", i);
5143                 if(StringToVariant(buf) == gameInfo.variant) break;
5144             }
5145             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5146             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5147             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5148         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5149         SendToICS(ics_prefix);
5150         SendToICS(buf);
5151         if(startedFromSetupPosition || backwardMostMove != 0) {
5152           fen = PositionToFEN(backwardMostMove, NULL);
5153           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5154             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5155             SendToICS(buf);
5156           } else { // FICS: everything has to set by separate bsetup commands
5157             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5158             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5159             SendToICS(buf);
5160             if(!WhiteOnMove(backwardMostMove)) {
5161                 SendToICS("bsetup tomove black\n");
5162             }
5163             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5164             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5165             SendToICS(buf);
5166             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5167             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5168             SendToICS(buf);
5169             i = boards[backwardMostMove][EP_STATUS];
5170             if(i >= 0) { // set e.p.
5171               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5172                 SendToICS(buf);
5173             }
5174             bsetup++;
5175           }
5176         }
5177       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5178             SendToICS("bsetup done\n"); // switch to normal examining.
5179     }
5180     for(i = backwardMostMove; i<last; i++) {
5181         char buf[20];
5182         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5183         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5184             int len = strlen(moveList[i]);
5185             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5186             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5187         }
5188         SendToICS(buf);
5189     }
5190     SendToICS(ics_prefix);
5191     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5192 }
5193
5194 void
5195 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5196 {
5197     if (rf == DROP_RANK) {
5198       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5199       sprintf(move, "%c@%c%c\n",
5200                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5201     } else {
5202         if (promoChar == 'x' || promoChar == NULLCHAR) {
5203           sprintf(move, "%c%c%c%c\n",
5204                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5205         } else {
5206             sprintf(move, "%c%c%c%c%c\n",
5207                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5208         }
5209     }
5210 }
5211
5212 void
5213 ProcessICSInitScript (FILE *f)
5214 {
5215     char buf[MSG_SIZ];
5216
5217     while (fgets(buf, MSG_SIZ, f)) {
5218         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5219     }
5220
5221     fclose(f);
5222 }
5223
5224
5225 static int lastX, lastY, selectFlag, dragging;
5226
5227 void
5228 Sweep (int step)
5229 {
5230     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5231     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5232     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5233     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5234     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5235     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5236     do {
5237         promoSweep -= step;
5238         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5239         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5240         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5241         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5242         if(!step) step = -1;
5243     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5244             appData.testLegality && (promoSweep == king ||
5245             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5246     if(toX >= 0) {
5247         int victim = boards[currentMove][toY][toX];
5248         boards[currentMove][toY][toX] = promoSweep;
5249         DrawPosition(FALSE, boards[currentMove]);
5250         boards[currentMove][toY][toX] = victim;
5251     } else
5252     ChangeDragPiece(promoSweep);
5253 }
5254
5255 int
5256 PromoScroll (int x, int y)
5257 {
5258   int step = 0;
5259
5260   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5261   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5262   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5263   if(!step) return FALSE;
5264   lastX = x; lastY = y;
5265   if((promoSweep < BlackPawn) == flipView) step = -step;
5266   if(step > 0) selectFlag = 1;
5267   if(!selectFlag) Sweep(step);
5268   return FALSE;
5269 }
5270
5271 void
5272 NextPiece (int step)
5273 {
5274     ChessSquare piece = boards[currentMove][toY][toX];
5275     do {
5276         pieceSweep -= step;
5277         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5278         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5279         if(!step) step = -1;
5280     } while(PieceToChar(pieceSweep) == '.');
5281     boards[currentMove][toY][toX] = pieceSweep;
5282     DrawPosition(FALSE, boards[currentMove]);
5283     boards[currentMove][toY][toX] = piece;
5284 }
5285 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5286 void
5287 AlphaRank (char *move, int n)
5288 {
5289 //    char *p = move, c; int x, y;
5290
5291     if (appData.debugMode) {
5292         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5293     }
5294
5295     if(move[1]=='*' &&
5296        move[2]>='0' && move[2]<='9' &&
5297        move[3]>='a' && move[3]<='x'    ) {
5298         move[1] = '@';
5299         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5300         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5301     } else
5302     if(move[0]>='0' && move[0]<='9' &&
5303        move[1]>='a' && move[1]<='x' &&
5304        move[2]>='0' && move[2]<='9' &&
5305        move[3]>='a' && move[3]<='x'    ) {
5306         /* input move, Shogi -> normal */
5307         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5308         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5309         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5310         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5311     } else
5312     if(move[1]=='@' &&
5313        move[3]>='0' && move[3]<='9' &&
5314        move[2]>='a' && move[2]<='x'    ) {
5315         move[1] = '*';
5316         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5317         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5318     } else
5319     if(
5320        move[0]>='a' && move[0]<='x' &&
5321        move[3]>='0' && move[3]<='9' &&
5322        move[2]>='a' && move[2]<='x'    ) {
5323          /* output move, normal -> Shogi */
5324         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5325         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5326         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5327         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5328         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5329     }
5330     if (appData.debugMode) {
5331         fprintf(debugFP, "   out = '%s'\n", move);
5332     }
5333 }
5334
5335 char yy_textstr[8000];
5336
5337 /* Parser for moves from gnuchess, ICS, or user typein box */
5338 Boolean
5339 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5340 {
5341     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5342
5343     switch (*moveType) {
5344       case WhitePromotion:
5345       case BlackPromotion:
5346       case WhiteNonPromotion:
5347       case BlackNonPromotion:
5348       case NormalMove:
5349       case WhiteCapturesEnPassant:
5350       case BlackCapturesEnPassant:
5351       case WhiteKingSideCastle:
5352       case WhiteQueenSideCastle:
5353       case BlackKingSideCastle:
5354       case BlackQueenSideCastle:
5355       case WhiteKingSideCastleWild:
5356       case WhiteQueenSideCastleWild:
5357       case BlackKingSideCastleWild:
5358       case BlackQueenSideCastleWild:
5359       /* Code added by Tord: */
5360       case WhiteHSideCastleFR:
5361       case WhiteASideCastleFR:
5362       case BlackHSideCastleFR:
5363       case BlackASideCastleFR:
5364       /* End of code added by Tord */
5365       case IllegalMove:         /* bug or odd chess variant */
5366         *fromX = currentMoveString[0] - AAA;
5367         *fromY = currentMoveString[1] - ONE;
5368         *toX = currentMoveString[2] - AAA;
5369         *toY = currentMoveString[3] - ONE;
5370         *promoChar = currentMoveString[4];
5371         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5372             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5373     if (appData.debugMode) {
5374         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5375     }
5376             *fromX = *fromY = *toX = *toY = 0;
5377             return FALSE;
5378         }
5379         if (appData.testLegality) {
5380           return (*moveType != IllegalMove);
5381         } else {
5382           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5383                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5384         }
5385
5386       case WhiteDrop:
5387       case BlackDrop:
5388         *fromX = *moveType == WhiteDrop ?
5389           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5390           (int) CharToPiece(ToLower(currentMoveString[0]));
5391         *fromY = DROP_RANK;
5392         *toX = currentMoveString[2] - AAA;
5393         *toY = currentMoveString[3] - ONE;
5394         *promoChar = NULLCHAR;
5395         return TRUE;
5396
5397       case AmbiguousMove:
5398       case ImpossibleMove:
5399       case EndOfFile:
5400       case ElapsedTime:
5401       case Comment:
5402       case PGNTag:
5403       case NAG:
5404       case WhiteWins:
5405       case BlackWins:
5406       case GameIsDrawn:
5407       default:
5408     if (appData.debugMode) {
5409         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5410     }
5411         /* bug? */
5412         *fromX = *fromY = *toX = *toY = 0;
5413         *promoChar = NULLCHAR;
5414         return FALSE;
5415     }
5416 }
5417
5418 Boolean pushed = FALSE;
5419 char *lastParseAttempt;
5420
5421 void
5422 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5423 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5424   int fromX, fromY, toX, toY; char promoChar;
5425   ChessMove moveType;
5426   Boolean valid;
5427   int nr = 0;
5428
5429   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5430   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5431     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5432     pushed = TRUE;
5433   }
5434   endPV = forwardMostMove;
5435   do {
5436     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5437     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5438     lastParseAttempt = pv;
5439     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5440     if(!valid && nr == 0 &&
5441        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5442         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5443         // Hande case where played move is different from leading PV move
5444         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5445         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5446         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5447         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5448           endPV += 2; // if position different, keep this
5449           moveList[endPV-1][0] = fromX + AAA;
5450           moveList[endPV-1][1] = fromY + ONE;
5451           moveList[endPV-1][2] = toX + AAA;
5452           moveList[endPV-1][3] = toY + ONE;
5453           parseList[endPV-1][0] = NULLCHAR;
5454           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5455         }
5456       }
5457     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5458     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5459     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5460     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5461         valid++; // allow comments in PV
5462         continue;
5463     }
5464     nr++;
5465     if(endPV+1 > framePtr) break; // no space, truncate
5466     if(!valid) break;
5467     endPV++;
5468     CopyBoard(boards[endPV], boards[endPV-1]);
5469     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5470     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5471     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5472     CoordsToAlgebraic(boards[endPV - 1],
5473                              PosFlags(endPV - 1),
5474                              fromY, fromX, toY, toX, promoChar,
5475                              parseList[endPV - 1]);
5476   } while(valid);
5477   if(atEnd == 2) return; // used hidden, for PV conversion
5478   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5479   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5480   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5481                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5482   DrawPosition(TRUE, boards[currentMove]);
5483 }
5484
5485 int
5486 MultiPV (ChessProgramState *cps)
5487 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5488         int i;
5489         for(i=0; i<cps->nrOptions; i++)
5490             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5491                 return i;
5492         return -1;
5493 }
5494
5495 Boolean
5496 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5497 {
5498         int startPV, multi, lineStart, origIndex = index;
5499         char *p, buf2[MSG_SIZ];
5500         ChessProgramState *cps = (pane ? &second : &first);
5501
5502         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5503         lastX = x; lastY = y;
5504         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5505         lineStart = startPV = index;
5506         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5507         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5508         index = startPV;
5509         do{ while(buf[index] && buf[index] != '\n') index++;
5510         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5511         buf[index] = 0;
5512         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5513                 int n = cps->option[multi].value;
5514                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5515                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5516                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5517                 cps->option[multi].value = n;
5518                 *start = *end = 0;
5519                 return FALSE;
5520         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5521                 ExcludeClick(origIndex - lineStart);
5522                 return FALSE;
5523         }
5524         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5525         *start = startPV; *end = index-1;
5526         return TRUE;
5527 }
5528
5529 char *
5530 PvToSAN (char *pv)
5531 {
5532         static char buf[10*MSG_SIZ];
5533         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5534         *buf = NULLCHAR;
5535         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5536         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5537         for(i = forwardMostMove; i<endPV; i++){
5538             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5539             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5540             k += strlen(buf+k);
5541         }
5542         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5543         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5544         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5545         endPV = savedEnd;
5546         return buf;
5547 }
5548
5549 Boolean
5550 LoadPV (int x, int y)
5551 { // called on right mouse click to load PV
5552   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5553   lastX = x; lastY = y;
5554   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5555   return TRUE;
5556 }
5557
5558 void
5559 UnLoadPV ()
5560 {
5561   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5562   if(endPV < 0) return;
5563   if(appData.autoCopyPV) CopyFENToClipboard();
5564   endPV = -1;
5565   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5566         Boolean saveAnimate = appData.animate;
5567         if(pushed) {
5568             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5569                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5570             } else storedGames--; // abandon shelved tail of original game
5571         }
5572         pushed = FALSE;
5573         forwardMostMove = currentMove;
5574         currentMove = oldFMM;
5575         appData.animate = FALSE;
5576         ToNrEvent(forwardMostMove);
5577         appData.animate = saveAnimate;
5578   }
5579   currentMove = forwardMostMove;
5580   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5581   ClearPremoveHighlights();
5582   DrawPosition(TRUE, boards[currentMove]);
5583 }
5584
5585 void
5586 MovePV (int x, int y, int h)
5587 { // step through PV based on mouse coordinates (called on mouse move)
5588   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5589
5590   // we must somehow check if right button is still down (might be released off board!)
5591   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5592   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5593   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5594   if(!step) return;
5595   lastX = x; lastY = y;
5596
5597   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5598   if(endPV < 0) return;
5599   if(y < margin) step = 1; else
5600   if(y > h - margin) step = -1;
5601   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5602   currentMove += step;
5603   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5604   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5605                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5606   DrawPosition(FALSE, boards[currentMove]);
5607 }
5608
5609
5610 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5611 // All positions will have equal probability, but the current method will not provide a unique
5612 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5613 #define DARK 1
5614 #define LITE 2
5615 #define ANY 3
5616
5617 int squaresLeft[4];
5618 int piecesLeft[(int)BlackPawn];
5619 int seed, nrOfShuffles;
5620
5621 void
5622 GetPositionNumber ()
5623 {       // sets global variable seed
5624         int i;
5625
5626         seed = appData.defaultFrcPosition;
5627         if(seed < 0) { // randomize based on time for negative FRC position numbers
5628                 for(i=0; i<50; i++) seed += random();
5629                 seed = random() ^ random() >> 8 ^ random() << 8;
5630                 if(seed<0) seed = -seed;
5631         }
5632 }
5633
5634 int
5635 put (Board board, int pieceType, int rank, int n, int shade)
5636 // put the piece on the (n-1)-th empty squares of the given shade
5637 {
5638         int i;
5639
5640         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5641                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5642                         board[rank][i] = (ChessSquare) pieceType;
5643                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5644                         squaresLeft[ANY]--;
5645                         piecesLeft[pieceType]--;
5646                         return i;
5647                 }
5648         }
5649         return -1;
5650 }
5651
5652
5653 void
5654 AddOnePiece (Board board, int pieceType, int rank, int shade)
5655 // calculate where the next piece goes, (any empty square), and put it there
5656 {
5657         int i;
5658
5659         i = seed % squaresLeft[shade];
5660         nrOfShuffles *= squaresLeft[shade];
5661         seed /= squaresLeft[shade];
5662         put(board, pieceType, rank, i, shade);
5663 }
5664
5665 void
5666 AddTwoPieces (Board board, int pieceType, int rank)
5667 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5668 {
5669         int i, n=squaresLeft[ANY], j=n-1, k;
5670
5671         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5672         i = seed % k;  // pick one
5673         nrOfShuffles *= k;
5674         seed /= k;
5675         while(i >= j) i -= j--;
5676         j = n - 1 - j; i += j;
5677         put(board, pieceType, rank, j, ANY);
5678         put(board, pieceType, rank, i, ANY);
5679 }
5680
5681 void
5682 SetUpShuffle (Board board, int number)
5683 {
5684         int i, p, first=1;
5685
5686         GetPositionNumber(); nrOfShuffles = 1;
5687
5688         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5689         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5690         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5691
5692         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5693
5694         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5695             p = (int) board[0][i];
5696             if(p < (int) BlackPawn) piecesLeft[p] ++;
5697             board[0][i] = EmptySquare;
5698         }
5699
5700         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5701             // shuffles restricted to allow normal castling put KRR first
5702             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5703                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5704             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5705                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5706             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5707                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5708             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5709                 put(board, WhiteRook, 0, 0, ANY);
5710             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5711         }
5712
5713         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5714             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5715             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5716                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5717                 while(piecesLeft[p] >= 2) {
5718                     AddOnePiece(board, p, 0, LITE);
5719                     AddOnePiece(board, p, 0, DARK);
5720                 }
5721                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5722             }
5723
5724         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5725             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5726             // but we leave King and Rooks for last, to possibly obey FRC restriction
5727             if(p == (int)WhiteRook) continue;
5728             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5729             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5730         }
5731
5732         // now everything is placed, except perhaps King (Unicorn) and Rooks
5733
5734         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5735             // Last King gets castling rights
5736             while(piecesLeft[(int)WhiteUnicorn]) {
5737                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5738                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5739             }
5740
5741             while(piecesLeft[(int)WhiteKing]) {
5742                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5743                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5744             }
5745
5746
5747         } else {
5748             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5749             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5750         }
5751
5752         // Only Rooks can be left; simply place them all
5753         while(piecesLeft[(int)WhiteRook]) {
5754                 i = put(board, WhiteRook, 0, 0, ANY);
5755                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5756                         if(first) {
5757                                 first=0;
5758                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5759                         }
5760                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5761                 }
5762         }
5763         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5764             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5765         }
5766
5767         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5768 }
5769
5770 int
5771 SetCharTable (char *table, const char * map)
5772 /* [HGM] moved here from winboard.c because of its general usefulness */
5773 /*       Basically a safe strcpy that uses the last character as King */
5774 {
5775     int result = FALSE; int NrPieces;
5776
5777     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5778                     && NrPieces >= 12 && !(NrPieces&1)) {
5779         int i; /* [HGM] Accept even length from 12 to 34 */
5780
5781         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5782         for( i=0; i<NrPieces/2-1; i++ ) {
5783             table[i] = map[i];
5784             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5785         }
5786         table[(int) WhiteKing]  = map[NrPieces/2-1];
5787         table[(int) BlackKing]  = map[NrPieces-1];
5788
5789         result = TRUE;
5790     }
5791
5792     return result;
5793 }
5794
5795 void
5796 Prelude (Board board)
5797 {       // [HGM] superchess: random selection of exo-pieces
5798         int i, j, k; ChessSquare p;
5799         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5800
5801         GetPositionNumber(); // use FRC position number
5802
5803         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5804             SetCharTable(pieceToChar, appData.pieceToCharTable);
5805             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5806                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5807         }
5808
5809         j = seed%4;                 seed /= 4;
5810         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5811         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5812         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5813         j = seed%3 + (seed%3 >= j); seed /= 3;
5814         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5815         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5816         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5817         j = seed%3;                 seed /= 3;
5818         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5819         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5820         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5821         j = seed%2 + (seed%2 >= j); seed /= 2;
5822         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5823         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5824         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5825         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5826         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5827         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5828         put(board, exoPieces[0],    0, 0, ANY);
5829         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5830 }
5831
5832 void
5833 InitPosition (int redraw)
5834 {
5835     ChessSquare (* pieces)[BOARD_FILES];
5836     int i, j, pawnRow, overrule,
5837     oldx = gameInfo.boardWidth,
5838     oldy = gameInfo.boardHeight,
5839     oldh = gameInfo.holdingsWidth;
5840     static int oldv;
5841
5842     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5843
5844     /* [AS] Initialize pv info list [HGM] and game status */
5845     {
5846         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5847             pvInfoList[i].depth = 0;
5848             boards[i][EP_STATUS] = EP_NONE;
5849             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5850         }
5851
5852         initialRulePlies = 0; /* 50-move counter start */
5853
5854         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5855         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5856     }
5857
5858
5859     /* [HGM] logic here is completely changed. In stead of full positions */
5860     /* the initialized data only consist of the two backranks. The switch */
5861     /* selects which one we will use, which is than copied to the Board   */
5862     /* initialPosition, which for the rest is initialized by Pawns and    */
5863     /* empty squares. This initial position is then copied to boards[0],  */
5864     /* possibly after shuffling, so that it remains available.            */
5865
5866     gameInfo.holdingsWidth = 0; /* default board sizes */
5867     gameInfo.boardWidth    = 8;
5868     gameInfo.boardHeight   = 8;
5869     gameInfo.holdingsSize  = 0;
5870     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5871     for(i=0; i<BOARD_FILES-2; i++)
5872       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5873     initialPosition[EP_STATUS] = EP_NONE;
5874     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5875     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5876          SetCharTable(pieceNickName, appData.pieceNickNames);
5877     else SetCharTable(pieceNickName, "............");
5878     pieces = FIDEArray;
5879
5880     switch (gameInfo.variant) {
5881     case VariantFischeRandom:
5882       shuffleOpenings = TRUE;
5883     default:
5884       break;
5885     case VariantShatranj:
5886       pieces = ShatranjArray;
5887       nrCastlingRights = 0;
5888       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5889       break;
5890     case VariantMakruk:
5891       pieces = makrukArray;
5892       nrCastlingRights = 0;
5893       startedFromSetupPosition = TRUE;
5894       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5895       break;
5896     case VariantTwoKings:
5897       pieces = twoKingsArray;
5898       break;
5899     case VariantGrand:
5900       pieces = GrandArray;
5901       nrCastlingRights = 0;
5902       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5903       gameInfo.boardWidth = 10;
5904       gameInfo.boardHeight = 10;
5905       gameInfo.holdingsSize = 7;
5906       break;
5907     case VariantCapaRandom:
5908       shuffleOpenings = TRUE;
5909     case VariantCapablanca:
5910       pieces = CapablancaArray;
5911       gameInfo.boardWidth = 10;
5912       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5913       break;
5914     case VariantGothic:
5915       pieces = GothicArray;
5916       gameInfo.boardWidth = 10;
5917       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5918       break;
5919     case VariantSChess:
5920       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5921       gameInfo.holdingsSize = 7;
5922       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5923       break;
5924     case VariantJanus:
5925       pieces = JanusArray;
5926       gameInfo.boardWidth = 10;
5927       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5928       nrCastlingRights = 6;
5929         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5930         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5931         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5932         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5933         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5934         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5935       break;
5936     case VariantFalcon:
5937       pieces = FalconArray;
5938       gameInfo.boardWidth = 10;
5939       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5940       break;
5941     case VariantXiangqi:
5942       pieces = XiangqiArray;
5943       gameInfo.boardWidth  = 9;
5944       gameInfo.boardHeight = 10;
5945       nrCastlingRights = 0;
5946       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5947       break;
5948     case VariantShogi:
5949       pieces = ShogiArray;
5950       gameInfo.boardWidth  = 9;
5951       gameInfo.boardHeight = 9;
5952       gameInfo.holdingsSize = 7;
5953       nrCastlingRights = 0;
5954       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5955       break;
5956     case VariantCourier:
5957       pieces = CourierArray;
5958       gameInfo.boardWidth  = 12;
5959       nrCastlingRights = 0;
5960       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5961       break;
5962     case VariantKnightmate:
5963       pieces = KnightmateArray;
5964       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5965       break;
5966     case VariantSpartan:
5967       pieces = SpartanArray;
5968       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5969       break;
5970     case VariantFairy:
5971       pieces = fairyArray;
5972       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5973       break;
5974     case VariantGreat:
5975       pieces = GreatArray;
5976       gameInfo.boardWidth = 10;
5977       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5978       gameInfo.holdingsSize = 8;
5979       break;
5980     case VariantSuper:
5981       pieces = FIDEArray;
5982       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5983       gameInfo.holdingsSize = 8;
5984       startedFromSetupPosition = TRUE;
5985       break;
5986     case VariantCrazyhouse:
5987     case VariantBughouse:
5988       pieces = FIDEArray;
5989       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5990       gameInfo.holdingsSize = 5;
5991       break;
5992     case VariantWildCastle:
5993       pieces = FIDEArray;
5994       /* !!?shuffle with kings guaranteed to be on d or e file */
5995       shuffleOpenings = 1;
5996       break;
5997     case VariantNoCastle:
5998       pieces = FIDEArray;
5999       nrCastlingRights = 0;
6000       /* !!?unconstrained back-rank shuffle */
6001       shuffleOpenings = 1;
6002       break;
6003     }
6004
6005     overrule = 0;
6006     if(appData.NrFiles >= 0) {
6007         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6008         gameInfo.boardWidth = appData.NrFiles;
6009     }
6010     if(appData.NrRanks >= 0) {
6011         gameInfo.boardHeight = appData.NrRanks;
6012     }
6013     if(appData.holdingsSize >= 0) {
6014         i = appData.holdingsSize;
6015         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6016         gameInfo.holdingsSize = i;
6017     }
6018     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6019     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6020         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6021
6022     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6023     if(pawnRow < 1) pawnRow = 1;
6024     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6025
6026     /* User pieceToChar list overrules defaults */
6027     if(appData.pieceToCharTable != NULL)
6028         SetCharTable(pieceToChar, appData.pieceToCharTable);
6029
6030     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6031
6032         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6033             s = (ChessSquare) 0; /* account holding counts in guard band */
6034         for( i=0; i<BOARD_HEIGHT; i++ )
6035             initialPosition[i][j] = s;
6036
6037         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6038         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6039         initialPosition[pawnRow][j] = WhitePawn;
6040         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6041         if(gameInfo.variant == VariantXiangqi) {
6042             if(j&1) {
6043                 initialPosition[pawnRow][j] =
6044                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6045                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6046                    initialPosition[2][j] = WhiteCannon;
6047                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6048                 }
6049             }
6050         }
6051         if(gameInfo.variant == VariantGrand) {
6052             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6053                initialPosition[0][j] = WhiteRook;
6054                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6055             }
6056         }
6057         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6058     }
6059     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6060
6061             j=BOARD_LEFT+1;
6062             initialPosition[1][j] = WhiteBishop;
6063             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6064             j=BOARD_RGHT-2;
6065             initialPosition[1][j] = WhiteRook;
6066             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6067     }
6068
6069     if( nrCastlingRights == -1) {
6070         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6071         /*       This sets default castling rights from none to normal corners   */
6072         /* Variants with other castling rights must set them themselves above    */
6073         nrCastlingRights = 6;
6074
6075         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6076         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6077         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6078         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6079         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6080         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6081      }
6082
6083      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6084      if(gameInfo.variant == VariantGreat) { // promotion commoners
6085         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6086         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6087         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6088         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6089      }
6090      if( gameInfo.variant == VariantSChess ) {
6091       initialPosition[1][0] = BlackMarshall;
6092       initialPosition[2][0] = BlackAngel;
6093       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6094       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6095       initialPosition[1][1] = initialPosition[2][1] =
6096       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6097      }
6098   if (appData.debugMode) {
6099     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6100   }
6101     if(shuffleOpenings) {
6102         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6103         startedFromSetupPosition = TRUE;
6104     }
6105     if(startedFromPositionFile) {
6106       /* [HGM] loadPos: use PositionFile for every new game */
6107       CopyBoard(initialPosition, filePosition);
6108       for(i=0; i<nrCastlingRights; i++)
6109           initialRights[i] = filePosition[CASTLING][i];
6110       startedFromSetupPosition = TRUE;
6111     }
6112
6113     CopyBoard(boards[0], initialPosition);
6114
6115     if(oldx != gameInfo.boardWidth ||
6116        oldy != gameInfo.boardHeight ||
6117        oldv != gameInfo.variant ||
6118        oldh != gameInfo.holdingsWidth
6119                                          )
6120             InitDrawingSizes(-2 ,0);
6121
6122     oldv = gameInfo.variant;
6123     if (redraw)
6124       DrawPosition(TRUE, boards[currentMove]);
6125 }
6126
6127 void
6128 SendBoard (ChessProgramState *cps, int moveNum)
6129 {
6130     char message[MSG_SIZ];
6131
6132     if (cps->useSetboard) {
6133       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6134       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6135       SendToProgram(message, cps);
6136       free(fen);
6137
6138     } else {
6139       ChessSquare *bp;
6140       int i, j, left=0, right=BOARD_WIDTH;
6141       /* Kludge to set black to move, avoiding the troublesome and now
6142        * deprecated "black" command.
6143        */
6144       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6145         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6146
6147       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6148
6149       SendToProgram("edit\n", cps);
6150       SendToProgram("#\n", cps);
6151       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6152         bp = &boards[moveNum][i][left];
6153         for (j = left; j < right; j++, bp++) {
6154           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6155           if ((int) *bp < (int) BlackPawn) {
6156             if(j == BOARD_RGHT+1)
6157                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6158             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6159             if(message[0] == '+' || message[0] == '~') {
6160               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6161                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6162                         AAA + j, ONE + i);
6163             }
6164             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6165                 message[1] = BOARD_RGHT   - 1 - j + '1';
6166                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6167             }
6168             SendToProgram(message, cps);
6169           }
6170         }
6171       }
6172
6173       SendToProgram("c\n", cps);
6174       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6175         bp = &boards[moveNum][i][left];
6176         for (j = left; j < right; j++, bp++) {
6177           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6178           if (((int) *bp != (int) EmptySquare)
6179               && ((int) *bp >= (int) BlackPawn)) {
6180             if(j == BOARD_LEFT-2)
6181                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6182             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6183                     AAA + j, ONE + i);
6184             if(message[0] == '+' || message[0] == '~') {
6185               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6186                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6187                         AAA + j, ONE + i);
6188             }
6189             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6190                 message[1] = BOARD_RGHT   - 1 - j + '1';
6191                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6192             }
6193             SendToProgram(message, cps);
6194           }
6195         }
6196       }
6197
6198       SendToProgram(".\n", cps);
6199     }
6200     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6201 }
6202
6203 char exclusionHeader[MSG_SIZ];
6204 int exCnt, excludePtr;
6205 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6206 static Exclusion excluTab[200];
6207 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6208
6209 static void
6210 WriteMap (int s)
6211 {
6212     int j;
6213     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6214     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6215 }
6216
6217 static void
6218 ClearMap ()
6219 {
6220     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6221     excludePtr = 24; exCnt = 0;
6222     WriteMap(0);
6223 }
6224
6225 static void
6226 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6227 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6228     char buf[2*MOVE_LEN], *p;
6229     Exclusion *e = excluTab;
6230     int i;
6231     for(i=0; i<exCnt; i++)
6232         if(e[i].ff == fromX && e[i].fr == fromY &&
6233            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6234     if(i == exCnt) { // was not in exclude list; add it
6235         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6236         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6237             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6238             return; // abort
6239         }
6240         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6241         excludePtr++; e[i].mark = excludePtr++;
6242         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6243         exCnt++;
6244     }
6245     exclusionHeader[e[i].mark] = state;
6246 }
6247
6248 static int
6249 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6250 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6251     char buf[MSG_SIZ];
6252     int j, k;
6253     ChessMove moveType;
6254     if((signed char)promoChar == -1) { // kludge to indicate best move
6255         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6256             return 1; // if unparsable, abort
6257     }
6258     // update exclusion map (resolving toggle by consulting existing state)
6259     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6260     j = k%8; k >>= 3;
6261     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6262     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6263          excludeMap[k] |=   1<<j;
6264     else excludeMap[k] &= ~(1<<j);
6265     // update header
6266     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6267     // inform engine
6268     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6269     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6270     SendToBoth(buf);
6271     return (state == '+');
6272 }
6273
6274 static void
6275 ExcludeClick (int index)
6276 {
6277     int i, j;
6278     Exclusion *e = excluTab;
6279     if(index < 25) { // none, best or tail clicked
6280         if(index < 13) { // none: include all
6281             WriteMap(0); // clear map
6282             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6283             SendToBoth("include all\n"); // and inform engine
6284         } else if(index > 18) { // tail
6285             if(exclusionHeader[19] == '-') { // tail was excluded
6286                 SendToBoth("include all\n");
6287                 WriteMap(0); // clear map completely
6288                 // now re-exclude selected moves
6289                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6290                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6291             } else { // tail was included or in mixed state
6292                 SendToBoth("exclude all\n");
6293                 WriteMap(0xFF); // fill map completely
6294                 // now re-include selected moves
6295                 j = 0; // count them
6296                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6297                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6298                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6299             }
6300         } else { // best
6301             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6302         }
6303     } else {
6304         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6305             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6306             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6307             break;
6308         }
6309     }
6310 }
6311
6312 ChessSquare
6313 DefaultPromoChoice (int white)
6314 {
6315     ChessSquare result;
6316     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6317         result = WhiteFerz; // no choice
6318     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6319         result= WhiteKing; // in Suicide Q is the last thing we want
6320     else if(gameInfo.variant == VariantSpartan)
6321         result = white ? WhiteQueen : WhiteAngel;
6322     else result = WhiteQueen;
6323     if(!white) result = WHITE_TO_BLACK result;
6324     return result;
6325 }
6326
6327 static int autoQueen; // [HGM] oneclick
6328
6329 int
6330 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6331 {
6332     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6333     /* [HGM] add Shogi promotions */
6334     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6335     ChessSquare piece;
6336     ChessMove moveType;
6337     Boolean premove;
6338
6339     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6340     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6341
6342     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6343       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6344         return FALSE;
6345
6346     piece = boards[currentMove][fromY][fromX];
6347     if(gameInfo.variant == VariantShogi) {
6348         promotionZoneSize = BOARD_HEIGHT/3;
6349         highestPromotingPiece = (int)WhiteFerz;
6350     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6351         promotionZoneSize = 3;
6352     }
6353
6354     // Treat Lance as Pawn when it is not representing Amazon
6355     if(gameInfo.variant != VariantSuper) {
6356         if(piece == WhiteLance) piece = WhitePawn; else
6357         if(piece == BlackLance) piece = BlackPawn;
6358     }
6359
6360     // next weed out all moves that do not touch the promotion zone at all
6361     if((int)piece >= BlackPawn) {
6362         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6363              return FALSE;
6364         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6365     } else {
6366         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6367            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6368     }
6369
6370     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6371
6372     // weed out mandatory Shogi promotions
6373     if(gameInfo.variant == VariantShogi) {
6374         if(piece >= BlackPawn) {
6375             if(toY == 0 && piece == BlackPawn ||
6376                toY == 0 && piece == BlackQueen ||
6377                toY <= 1 && piece == BlackKnight) {
6378                 *promoChoice = '+';
6379                 return FALSE;
6380             }
6381         } else {
6382             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6383                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6384                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6385                 *promoChoice = '+';
6386                 return FALSE;
6387             }
6388         }
6389     }
6390
6391     // weed out obviously illegal Pawn moves
6392     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6393         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6394         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6395         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6396         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6397         // note we are not allowed to test for valid (non-)capture, due to premove
6398     }
6399
6400     // we either have a choice what to promote to, or (in Shogi) whether to promote
6401     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6402         *promoChoice = PieceToChar(BlackFerz);  // no choice
6403         return FALSE;
6404     }
6405     // no sense asking what we must promote to if it is going to explode...
6406     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6407         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6408         return FALSE;
6409     }
6410     // give caller the default choice even if we will not make it
6411     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6412     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6413     if(        sweepSelect && gameInfo.variant != VariantGreat
6414                            && gameInfo.variant != VariantGrand
6415                            && gameInfo.variant != VariantSuper) return FALSE;
6416     if(autoQueen) return FALSE; // predetermined
6417
6418     // suppress promotion popup on illegal moves that are not premoves
6419     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6420               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6421     if(appData.testLegality && !premove) {
6422         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6423                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6424         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6425             return FALSE;
6426     }
6427
6428     return TRUE;
6429 }
6430
6431 int
6432 InPalace (int row, int column)
6433 {   /* [HGM] for Xiangqi */
6434     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6435          column < (BOARD_WIDTH + 4)/2 &&
6436          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6437     return FALSE;
6438 }
6439
6440 int
6441 PieceForSquare (int x, int y)
6442 {
6443   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6444      return -1;
6445   else
6446      return boards[currentMove][y][x];
6447 }
6448
6449 int
6450 OKToStartUserMove (int x, int y)
6451 {
6452     ChessSquare from_piece;
6453     int white_piece;
6454
6455     if (matchMode) return FALSE;
6456     if (gameMode == EditPosition) return TRUE;
6457
6458     if (x >= 0 && y >= 0)
6459       from_piece = boards[currentMove][y][x];
6460     else
6461       from_piece = EmptySquare;
6462
6463     if (from_piece == EmptySquare) return FALSE;
6464
6465     white_piece = (int)from_piece >= (int)WhitePawn &&
6466       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6467
6468     switch (gameMode) {
6469       case AnalyzeFile:
6470       case TwoMachinesPlay:
6471       case EndOfGame:
6472         return FALSE;
6473
6474       case IcsObserving:
6475       case IcsIdle:
6476         return FALSE;
6477
6478       case MachinePlaysWhite:
6479       case IcsPlayingBlack:
6480         if (appData.zippyPlay) return FALSE;
6481         if (white_piece) {
6482             DisplayMoveError(_("You are playing Black"));
6483             return FALSE;
6484         }
6485         break;
6486
6487       case MachinePlaysBlack:
6488       case IcsPlayingWhite:
6489         if (appData.zippyPlay) return FALSE;
6490         if (!white_piece) {
6491             DisplayMoveError(_("You are playing White"));
6492             return FALSE;
6493         }
6494         break;
6495
6496       case PlayFromGameFile:
6497             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6498       case EditGame:
6499         if (!white_piece && WhiteOnMove(currentMove)) {
6500             DisplayMoveError(_("It is White's turn"));
6501             return FALSE;
6502         }
6503         if (white_piece && !WhiteOnMove(currentMove)) {
6504             DisplayMoveError(_("It is Black's turn"));
6505             return FALSE;
6506         }
6507         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6508             /* Editing correspondence game history */
6509             /* Could disallow this or prompt for confirmation */
6510             cmailOldMove = -1;
6511         }
6512         break;
6513
6514       case BeginningOfGame:
6515         if (appData.icsActive) return FALSE;
6516         if (!appData.noChessProgram) {
6517             if (!white_piece) {
6518                 DisplayMoveError(_("You are playing White"));
6519                 return FALSE;
6520             }
6521         }
6522         break;
6523
6524       case Training:
6525         if (!white_piece && WhiteOnMove(currentMove)) {
6526             DisplayMoveError(_("It is White's turn"));
6527             return FALSE;
6528         }
6529         if (white_piece && !WhiteOnMove(currentMove)) {
6530             DisplayMoveError(_("It is Black's turn"));
6531             return FALSE;
6532         }
6533         break;
6534
6535       default:
6536       case IcsExamining:
6537         break;
6538     }
6539     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6540         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6541         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6542         && gameMode != AnalyzeFile && gameMode != Training) {
6543         DisplayMoveError(_("Displayed position is not current"));
6544         return FALSE;
6545     }
6546     return TRUE;
6547 }
6548
6549 Boolean
6550 OnlyMove (int *x, int *y, Boolean captures)
6551 {
6552     DisambiguateClosure cl;
6553     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6554     switch(gameMode) {
6555       case MachinePlaysBlack:
6556       case IcsPlayingWhite:
6557       case BeginningOfGame:
6558         if(!WhiteOnMove(currentMove)) return FALSE;
6559         break;
6560       case MachinePlaysWhite:
6561       case IcsPlayingBlack:
6562         if(WhiteOnMove(currentMove)) return FALSE;
6563         break;
6564       case EditGame:
6565         break;
6566       default:
6567         return FALSE;
6568     }
6569     cl.pieceIn = EmptySquare;
6570     cl.rfIn = *y;
6571     cl.ffIn = *x;
6572     cl.rtIn = -1;
6573     cl.ftIn = -1;
6574     cl.promoCharIn = NULLCHAR;
6575     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6576     if( cl.kind == NormalMove ||
6577         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6578         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6579         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6580       fromX = cl.ff;
6581       fromY = cl.rf;
6582       *x = cl.ft;
6583       *y = cl.rt;
6584       return TRUE;
6585     }
6586     if(cl.kind != ImpossibleMove) return FALSE;
6587     cl.pieceIn = EmptySquare;
6588     cl.rfIn = -1;
6589     cl.ffIn = -1;
6590     cl.rtIn = *y;
6591     cl.ftIn = *x;
6592     cl.promoCharIn = NULLCHAR;
6593     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6594     if( cl.kind == NormalMove ||
6595         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6596         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6597         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6598       fromX = cl.ff;
6599       fromY = cl.rf;
6600       *x = cl.ft;
6601       *y = cl.rt;
6602       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6603       return TRUE;
6604     }
6605     return FALSE;
6606 }
6607
6608 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6609 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6610 int lastLoadGameUseList = FALSE;
6611 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6612 ChessMove lastLoadGameStart = EndOfFile;
6613 int doubleClick;
6614
6615 void
6616 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6617 {
6618     ChessMove moveType;
6619     ChessSquare pup;
6620     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6621
6622     /* Check if the user is playing in turn.  This is complicated because we
6623        let the user "pick up" a piece before it is his turn.  So the piece he
6624        tried to pick up may have been captured by the time he puts it down!
6625        Therefore we use the color the user is supposed to be playing in this
6626        test, not the color of the piece that is currently on the starting
6627        square---except in EditGame mode, where the user is playing both
6628        sides; fortunately there the capture race can't happen.  (It can
6629        now happen in IcsExamining mode, but that's just too bad.  The user
6630        will get a somewhat confusing message in that case.)
6631        */
6632
6633     switch (gameMode) {
6634       case AnalyzeFile:
6635       case TwoMachinesPlay:
6636       case EndOfGame:
6637       case IcsObserving:
6638       case IcsIdle:
6639         /* We switched into a game mode where moves are not accepted,
6640            perhaps while the mouse button was down. */
6641         return;
6642
6643       case MachinePlaysWhite:
6644         /* User is moving for Black */
6645         if (WhiteOnMove(currentMove)) {
6646             DisplayMoveError(_("It is White's turn"));
6647             return;
6648         }
6649         break;
6650
6651       case MachinePlaysBlack:
6652         /* User is moving for White */
6653         if (!WhiteOnMove(currentMove)) {
6654             DisplayMoveError(_("It is Black's turn"));
6655             return;
6656         }
6657         break;
6658
6659       case PlayFromGameFile:
6660             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6661       case EditGame:
6662       case IcsExamining:
6663       case BeginningOfGame:
6664       case AnalyzeMode:
6665       case Training:
6666         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6667         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6668             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6669             /* User is moving for Black */
6670             if (WhiteOnMove(currentMove)) {
6671                 DisplayMoveError(_("It is White's turn"));
6672                 return;
6673             }
6674         } else {
6675             /* User is moving for White */
6676             if (!WhiteOnMove(currentMove)) {
6677                 DisplayMoveError(_("It is Black's turn"));
6678                 return;
6679             }
6680         }
6681         break;
6682
6683       case IcsPlayingBlack:
6684         /* User is moving for Black */
6685         if (WhiteOnMove(currentMove)) {
6686             if (!appData.premove) {
6687                 DisplayMoveError(_("It is White's turn"));
6688             } else if (toX >= 0 && toY >= 0) {
6689                 premoveToX = toX;
6690                 premoveToY = toY;
6691                 premoveFromX = fromX;
6692                 premoveFromY = fromY;
6693                 premovePromoChar = promoChar;
6694                 gotPremove = 1;
6695                 if (appData.debugMode)
6696                     fprintf(debugFP, "Got premove: fromX %d,"
6697                             "fromY %d, toX %d, toY %d\n",
6698                             fromX, fromY, toX, toY);
6699             }
6700             return;
6701         }
6702         break;
6703
6704       case IcsPlayingWhite:
6705         /* User is moving for White */
6706         if (!WhiteOnMove(currentMove)) {
6707             if (!appData.premove) {
6708                 DisplayMoveError(_("It is Black's turn"));
6709             } else if (toX >= 0 && toY >= 0) {
6710                 premoveToX = toX;
6711                 premoveToY = toY;
6712                 premoveFromX = fromX;
6713                 premoveFromY = fromY;
6714                 premovePromoChar = promoChar;
6715                 gotPremove = 1;
6716                 if (appData.debugMode)
6717                     fprintf(debugFP, "Got premove: fromX %d,"
6718                             "fromY %d, toX %d, toY %d\n",
6719                             fromX, fromY, toX, toY);
6720             }
6721             return;
6722         }
6723         break;
6724
6725       default:
6726         break;
6727
6728       case EditPosition:
6729         /* EditPosition, empty square, or different color piece;
6730            click-click move is possible */
6731         if (toX == -2 || toY == -2) {
6732             boards[0][fromY][fromX] = EmptySquare;
6733             DrawPosition(FALSE, boards[currentMove]);
6734             return;
6735         } else if (toX >= 0 && toY >= 0) {
6736             boards[0][toY][toX] = boards[0][fromY][fromX];
6737             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6738                 if(boards[0][fromY][0] != EmptySquare) {
6739                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6740                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6741                 }
6742             } else
6743             if(fromX == BOARD_RGHT+1) {
6744                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6745                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6746                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6747                 }
6748             } else
6749             boards[0][fromY][fromX] = gatingPiece;
6750             DrawPosition(FALSE, boards[currentMove]);
6751             return;
6752         }
6753         return;
6754     }
6755
6756     if(toX < 0 || toY < 0) return;
6757     pup = boards[currentMove][toY][toX];
6758
6759     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6760     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6761          if( pup != EmptySquare ) return;
6762          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6763            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6764                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6765            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6766            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6767            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6768            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6769          fromY = DROP_RANK;
6770     }
6771
6772     /* [HGM] always test for legality, to get promotion info */
6773     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6774                                          fromY, fromX, toY, toX, promoChar);
6775
6776     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6777
6778     /* [HGM] but possibly ignore an IllegalMove result */
6779     if (appData.testLegality) {
6780         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6781             DisplayMoveError(_("Illegal move"));
6782             return;
6783         }
6784     }
6785
6786     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6787         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6788              ClearPremoveHighlights(); // was included
6789         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6790         return;
6791     }
6792
6793     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6794 }
6795
6796 /* Common tail of UserMoveEvent and DropMenuEvent */
6797 int
6798 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6799 {
6800     char *bookHit = 0;
6801
6802     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6803         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6804         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6805         if(WhiteOnMove(currentMove)) {
6806             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6807         } else {
6808             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6809         }
6810     }
6811
6812     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6813        move type in caller when we know the move is a legal promotion */
6814     if(moveType == NormalMove && promoChar)
6815         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6816
6817     /* [HGM] <popupFix> The following if has been moved here from
6818        UserMoveEvent(). Because it seemed to belong here (why not allow
6819        piece drops in training games?), and because it can only be
6820        performed after it is known to what we promote. */
6821     if (gameMode == Training) {
6822       /* compare the move played on the board to the next move in the
6823        * game. If they match, display the move and the opponent's response.
6824        * If they don't match, display an error message.
6825        */
6826       int saveAnimate;
6827       Board testBoard;
6828       CopyBoard(testBoard, boards[currentMove]);
6829       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6830
6831       if (CompareBoards(testBoard, boards[currentMove+1])) {
6832         ForwardInner(currentMove+1);
6833
6834         /* Autoplay the opponent's response.
6835          * if appData.animate was TRUE when Training mode was entered,
6836          * the response will be animated.
6837          */
6838         saveAnimate = appData.animate;
6839         appData.animate = animateTraining;
6840         ForwardInner(currentMove+1);
6841         appData.animate = saveAnimate;
6842
6843         /* check for the end of the game */
6844         if (currentMove >= forwardMostMove) {
6845           gameMode = PlayFromGameFile;
6846           ModeHighlight();
6847           SetTrainingModeOff();
6848           DisplayInformation(_("End of game"));
6849         }
6850       } else {
6851         DisplayError(_("Incorrect move"), 0);
6852       }
6853       return 1;
6854     }
6855
6856   /* Ok, now we know that the move is good, so we can kill
6857      the previous line in Analysis Mode */
6858   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6859                                 && currentMove < forwardMostMove) {
6860     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6861     else forwardMostMove = currentMove;
6862   }
6863
6864   ClearMap();
6865
6866   /* If we need the chess program but it's dead, restart it */
6867   ResurrectChessProgram();
6868
6869   /* A user move restarts a paused game*/
6870   if (pausing)
6871     PauseEvent();
6872
6873   thinkOutput[0] = NULLCHAR;
6874
6875   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6876
6877   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6878     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6879     return 1;
6880   }
6881
6882   if (gameMode == BeginningOfGame) {
6883     if (appData.noChessProgram) {
6884       gameMode = EditGame;
6885       SetGameInfo();
6886     } else {
6887       char buf[MSG_SIZ];
6888       gameMode = MachinePlaysBlack;
6889       StartClocks();
6890       SetGameInfo();
6891       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6892       DisplayTitle(buf);
6893       if (first.sendName) {
6894         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6895         SendToProgram(buf, &first);
6896       }
6897       StartClocks();
6898     }
6899     ModeHighlight();
6900   }
6901
6902   /* Relay move to ICS or chess engine */
6903   if (appData.icsActive) {
6904     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6905         gameMode == IcsExamining) {
6906       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6907         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6908         SendToICS("draw ");
6909         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6910       }
6911       // also send plain move, in case ICS does not understand atomic claims
6912       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6913       ics_user_moved = 1;
6914     }
6915   } else {
6916     if (first.sendTime && (gameMode == BeginningOfGame ||
6917                            gameMode == MachinePlaysWhite ||
6918                            gameMode == MachinePlaysBlack)) {
6919       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6920     }
6921     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6922          // [HGM] book: if program might be playing, let it use book
6923         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6924         first.maybeThinking = TRUE;
6925     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6926         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6927         SendBoard(&first, currentMove+1);
6928         if(second.analyzing) {
6929             if(!second.useSetboard) SendToProgram("undo\n", &second);
6930             SendBoard(&second, currentMove+1);
6931         }
6932     } else {
6933         SendMoveToProgram(forwardMostMove-1, &first);
6934         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6935     }
6936     if (currentMove == cmailOldMove + 1) {
6937       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6938     }
6939   }
6940
6941   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6942
6943   switch (gameMode) {
6944   case EditGame:
6945     if(appData.testLegality)
6946     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6947     case MT_NONE:
6948     case MT_CHECK:
6949       break;
6950     case MT_CHECKMATE:
6951     case MT_STAINMATE:
6952       if (WhiteOnMove(currentMove)) {
6953         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6954       } else {
6955         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6956       }
6957       break;
6958     case MT_STALEMATE:
6959       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6960       break;
6961     }
6962     break;
6963
6964   case MachinePlaysBlack:
6965   case MachinePlaysWhite:
6966     /* disable certain menu options while machine is thinking */
6967     SetMachineThinkingEnables();
6968     break;
6969
6970   default:
6971     break;
6972   }
6973
6974   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6975   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6976
6977   if(bookHit) { // [HGM] book: simulate book reply
6978         static char bookMove[MSG_SIZ]; // a bit generous?
6979
6980         programStats.nodes = programStats.depth = programStats.time =
6981         programStats.score = programStats.got_only_move = 0;
6982         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6983
6984         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6985         strcat(bookMove, bookHit);
6986         HandleMachineMove(bookMove, &first);
6987   }
6988   return 1;
6989 }
6990
6991 void
6992 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6993 {
6994     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6995     Markers *m = (Markers *) closure;
6996     if(rf == fromY && ff == fromX)
6997         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6998                          || kind == WhiteCapturesEnPassant
6999                          || kind == BlackCapturesEnPassant);
7000     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7001 }
7002
7003 void
7004 MarkTargetSquares (int clear)
7005 {
7006   int x, y;
7007   if(clear) // no reason to ever suppress clearing
7008     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7009   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7010      !appData.testLegality || gameMode == EditPosition) return;
7011   if(!clear) {
7012     int capt = 0;
7013     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7014     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7015       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7016       if(capt)
7017       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7018     }
7019   }
7020   DrawPosition(FALSE, NULL);
7021 }
7022
7023 int
7024 Explode (Board board, int fromX, int fromY, int toX, int toY)
7025 {
7026     if(gameInfo.variant == VariantAtomic &&
7027        (board[toY][toX] != EmptySquare ||                     // capture?
7028         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7029                          board[fromY][fromX] == BlackPawn   )
7030       )) {
7031         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7032         return TRUE;
7033     }
7034     return FALSE;
7035 }
7036
7037 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7038
7039 int
7040 CanPromote (ChessSquare piece, int y)
7041 {
7042         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7043         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7044         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7045            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7046            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7047                                                   gameInfo.variant == VariantMakruk) return FALSE;
7048         return (piece == BlackPawn && y == 1 ||
7049                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7050                 piece == BlackLance && y == 1 ||
7051                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7052 }
7053
7054 void
7055 LeftClick (ClickType clickType, int xPix, int yPix)
7056 {
7057     int x, y;
7058     Boolean saveAnimate;
7059     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7060     char promoChoice = NULLCHAR;
7061     ChessSquare piece;
7062     static TimeMark lastClickTime, prevClickTime;
7063
7064     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7065
7066     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7067
7068     if (clickType == Press) ErrorPopDown();
7069
7070     x = EventToSquare(xPix, BOARD_WIDTH);
7071     y = EventToSquare(yPix, BOARD_HEIGHT);
7072     if (!flipView && y >= 0) {
7073         y = BOARD_HEIGHT - 1 - y;
7074     }
7075     if (flipView && x >= 0) {
7076         x = BOARD_WIDTH - 1 - x;
7077     }
7078
7079     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7080         defaultPromoChoice = promoSweep;
7081         promoSweep = EmptySquare;   // terminate sweep
7082         promoDefaultAltered = TRUE;
7083         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7084     }
7085
7086     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7087         if(clickType == Release) return; // ignore upclick of click-click destination
7088         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7089         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7090         if(gameInfo.holdingsWidth &&
7091                 (WhiteOnMove(currentMove)
7092                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7093                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7094             // click in right holdings, for determining promotion piece
7095             ChessSquare p = boards[currentMove][y][x];
7096             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7097             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7098             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7099                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7100                 fromX = fromY = -1;
7101                 return;
7102             }
7103         }
7104         DrawPosition(FALSE, boards[currentMove]);
7105         return;
7106     }
7107
7108     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7109     if(clickType == Press
7110             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7111               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7112               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7113         return;
7114
7115     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7116         // could be static click on premove from-square: abort premove
7117         gotPremove = 0;
7118         ClearPremoveHighlights();
7119     }
7120
7121     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7122         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7123
7124     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7125         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7126                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7127         defaultPromoChoice = DefaultPromoChoice(side);
7128     }
7129
7130     autoQueen = appData.alwaysPromoteToQueen;
7131
7132     if (fromX == -1) {
7133       int originalY = y;
7134       gatingPiece = EmptySquare;
7135       if (clickType != Press) {
7136         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7137             DragPieceEnd(xPix, yPix); dragging = 0;
7138             DrawPosition(FALSE, NULL);
7139         }
7140         return;
7141       }
7142       doubleClick = FALSE;
7143       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7144         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7145       }
7146       fromX = x; fromY = y; toX = toY = -1;
7147       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7148          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7149          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7150             /* First square */
7151             if (OKToStartUserMove(fromX, fromY)) {
7152                 second = 0;
7153                 MarkTargetSquares(0);
7154                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7155                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7156                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7157                     promoSweep = defaultPromoChoice;
7158                     selectFlag = 0; lastX = xPix; lastY = yPix;
7159                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7160                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7161                 }
7162                 if (appData.highlightDragging) {
7163                     SetHighlights(fromX, fromY, -1, -1);
7164                 } else {
7165                     ClearHighlights();
7166                 }
7167             } else fromX = fromY = -1;
7168             return;
7169         }
7170     }
7171
7172     /* fromX != -1 */
7173     if (clickType == Press && gameMode != EditPosition) {
7174         ChessSquare fromP;
7175         ChessSquare toP;
7176         int frc;
7177
7178         // ignore off-board to clicks
7179         if(y < 0 || x < 0) return;
7180
7181         /* Check if clicking again on the same color piece */
7182         fromP = boards[currentMove][fromY][fromX];
7183         toP = boards[currentMove][y][x];
7184         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7185         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7186              WhitePawn <= toP && toP <= WhiteKing &&
7187              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7188              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7189             (BlackPawn <= fromP && fromP <= BlackKing &&
7190              BlackPawn <= toP && toP <= BlackKing &&
7191              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7192              !(fromP == BlackKing && toP == BlackRook && frc))) {
7193             /* Clicked again on same color piece -- changed his mind */
7194             second = (x == fromX && y == fromY);
7195             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7196                 second = FALSE; // first double-click rather than scond click
7197                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7198             }
7199             promoDefaultAltered = FALSE;
7200             MarkTargetSquares(1);
7201            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7202             if (appData.highlightDragging) {
7203                 SetHighlights(x, y, -1, -1);
7204             } else {
7205                 ClearHighlights();
7206             }
7207             if (OKToStartUserMove(x, y)) {
7208                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7209                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7210                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7211                  gatingPiece = boards[currentMove][fromY][fromX];
7212                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7213                 fromX = x;
7214                 fromY = y; dragging = 1;
7215                 MarkTargetSquares(0);
7216                 DragPieceBegin(xPix, yPix, FALSE);
7217                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7218                     promoSweep = defaultPromoChoice;
7219                     selectFlag = 0; lastX = xPix; lastY = yPix;
7220                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7221                 }
7222             }
7223            }
7224            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7225            second = FALSE;
7226         }
7227         // ignore clicks on holdings
7228         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7229     }
7230
7231     if (clickType == Release && x == fromX && y == fromY) {
7232         DragPieceEnd(xPix, yPix); dragging = 0;
7233         if(clearFlag) {
7234             // a deferred attempt to click-click move an empty square on top of a piece
7235             boards[currentMove][y][x] = EmptySquare;
7236             ClearHighlights();
7237             DrawPosition(FALSE, boards[currentMove]);
7238             fromX = fromY = -1; clearFlag = 0;
7239             return;
7240         }
7241         if (appData.animateDragging) {
7242             /* Undo animation damage if any */
7243             DrawPosition(FALSE, NULL);
7244         }
7245         if (second || sweepSelecting) {
7246             /* Second up/down in same square; just abort move */
7247             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7248             second = sweepSelecting = 0;
7249             fromX = fromY = -1;
7250             gatingPiece = EmptySquare;
7251             ClearHighlights();
7252             gotPremove = 0;
7253             ClearPremoveHighlights();
7254         } else {
7255             /* First upclick in same square; start click-click mode */
7256             SetHighlights(x, y, -1, -1);
7257         }
7258         return;
7259     }
7260
7261     clearFlag = 0;
7262
7263     /* we now have a different from- and (possibly off-board) to-square */
7264     /* Completed move */
7265     if(!sweepSelecting) {
7266         toX = x;
7267         toY = y;
7268     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7269
7270     saveAnimate = appData.animate;
7271     if (clickType == Press) {
7272         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7273             // must be Edit Position mode with empty-square selected
7274             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7275             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7276             return;
7277         }
7278         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7279           if(appData.sweepSelect) {
7280             ChessSquare piece = boards[currentMove][fromY][fromX];
7281             promoSweep = defaultPromoChoice;
7282             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7283             selectFlag = 0; lastX = xPix; lastY = yPix;
7284             Sweep(0); // Pawn that is going to promote: preview promotion piece
7285             sweepSelecting = 1;
7286             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7287             MarkTargetSquares(1);
7288           }
7289           return; // promo popup appears on up-click
7290         }
7291         /* Finish clickclick move */
7292         if (appData.animate || appData.highlightLastMove) {
7293             SetHighlights(fromX, fromY, toX, toY);
7294         } else {
7295             ClearHighlights();
7296         }
7297     } else {
7298 #if 0
7299 // [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
7300         /* Finish drag move */
7301         if (appData.highlightLastMove) {
7302             SetHighlights(fromX, fromY, toX, toY);
7303         } else {
7304             ClearHighlights();
7305         }
7306 #endif
7307         DragPieceEnd(xPix, yPix); dragging = 0;
7308         /* Don't animate move and drag both */
7309         appData.animate = FALSE;
7310     }
7311
7312     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7313     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7314         ChessSquare piece = boards[currentMove][fromY][fromX];
7315         if(gameMode == EditPosition && piece != EmptySquare &&
7316            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7317             int n;
7318
7319             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7320                 n = PieceToNumber(piece - (int)BlackPawn);
7321                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7322                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7323                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7324             } else
7325             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7326                 n = PieceToNumber(piece);
7327                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7328                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7329                 boards[currentMove][n][BOARD_WIDTH-2]++;
7330             }
7331             boards[currentMove][fromY][fromX] = EmptySquare;
7332         }
7333         ClearHighlights();
7334         fromX = fromY = -1;
7335         MarkTargetSquares(1);
7336         DrawPosition(TRUE, boards[currentMove]);
7337         return;
7338     }
7339
7340     // off-board moves should not be highlighted
7341     if(x < 0 || y < 0) ClearHighlights();
7342
7343     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7344
7345     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7346         SetHighlights(fromX, fromY, toX, toY);
7347         MarkTargetSquares(1);
7348         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7349             // [HGM] super: promotion to captured piece selected from holdings
7350             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7351             promotionChoice = TRUE;
7352             // kludge follows to temporarily execute move on display, without promoting yet
7353             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7354             boards[currentMove][toY][toX] = p;
7355             DrawPosition(FALSE, boards[currentMove]);
7356             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7357             boards[currentMove][toY][toX] = q;
7358             DisplayMessage("Click in holdings to choose piece", "");
7359             return;
7360         }
7361         PromotionPopUp();
7362     } else {
7363         int oldMove = currentMove;
7364         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7365         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7366         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7367         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7368            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7369             DrawPosition(TRUE, boards[currentMove]);
7370         MarkTargetSquares(1);
7371         fromX = fromY = -1;
7372     }
7373     appData.animate = saveAnimate;
7374     if (appData.animate || appData.animateDragging) {
7375         /* Undo animation damage if needed */
7376         DrawPosition(FALSE, NULL);
7377     }
7378 }
7379
7380 int
7381 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7382 {   // front-end-free part taken out of PieceMenuPopup
7383     int whichMenu; int xSqr, ySqr;
7384
7385     if(seekGraphUp) { // [HGM] seekgraph
7386         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7387         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7388         return -2;
7389     }
7390
7391     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7392          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7393         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7394         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7395         if(action == Press)   {
7396             originalFlip = flipView;
7397             flipView = !flipView; // temporarily flip board to see game from partners perspective
7398             DrawPosition(TRUE, partnerBoard);
7399             DisplayMessage(partnerStatus, "");
7400             partnerUp = TRUE;
7401         } else if(action == Release) {
7402             flipView = originalFlip;
7403             DrawPosition(TRUE, boards[currentMove]);
7404             partnerUp = FALSE;
7405         }
7406         return -2;
7407     }
7408
7409     xSqr = EventToSquare(x, BOARD_WIDTH);
7410     ySqr = EventToSquare(y, BOARD_HEIGHT);
7411     if (action == Release) {
7412         if(pieceSweep != EmptySquare) {
7413             EditPositionMenuEvent(pieceSweep, toX, toY);
7414             pieceSweep = EmptySquare;
7415         } else UnLoadPV(); // [HGM] pv
7416     }
7417     if (action != Press) return -2; // return code to be ignored
7418     switch (gameMode) {
7419       case IcsExamining:
7420         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7421       case EditPosition:
7422         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7423         if (xSqr < 0 || ySqr < 0) return -1;
7424         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7425         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7426         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7427         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7428         NextPiece(0);
7429         return 2; // grab
7430       case IcsObserving:
7431         if(!appData.icsEngineAnalyze) return -1;
7432       case IcsPlayingWhite:
7433       case IcsPlayingBlack:
7434         if(!appData.zippyPlay) goto noZip;
7435       case AnalyzeMode:
7436       case AnalyzeFile:
7437       case MachinePlaysWhite:
7438       case MachinePlaysBlack:
7439       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7440         if (!appData.dropMenu) {
7441           LoadPV(x, y);
7442           return 2; // flag front-end to grab mouse events
7443         }
7444         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7445            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7446       case EditGame:
7447       noZip:
7448         if (xSqr < 0 || ySqr < 0) return -1;
7449         if (!appData.dropMenu || appData.testLegality &&
7450             gameInfo.variant != VariantBughouse &&
7451             gameInfo.variant != VariantCrazyhouse) return -1;
7452         whichMenu = 1; // drop menu
7453         break;
7454       default:
7455         return -1;
7456     }
7457
7458     if (((*fromX = xSqr) < 0) ||
7459         ((*fromY = ySqr) < 0)) {
7460         *fromX = *fromY = -1;
7461         return -1;
7462     }
7463     if (flipView)
7464       *fromX = BOARD_WIDTH - 1 - *fromX;
7465     else
7466       *fromY = BOARD_HEIGHT - 1 - *fromY;
7467
7468     return whichMenu;
7469 }
7470
7471 void
7472 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7473 {
7474 //    char * hint = lastHint;
7475     FrontEndProgramStats stats;
7476
7477     stats.which = cps == &first ? 0 : 1;
7478     stats.depth = cpstats->depth;
7479     stats.nodes = cpstats->nodes;
7480     stats.score = cpstats->score;
7481     stats.time = cpstats->time;
7482     stats.pv = cpstats->movelist;
7483     stats.hint = lastHint;
7484     stats.an_move_index = 0;
7485     stats.an_move_count = 0;
7486
7487     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7488         stats.hint = cpstats->move_name;
7489         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7490         stats.an_move_count = cpstats->nr_moves;
7491     }
7492
7493     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
7494
7495     SetProgramStats( &stats );
7496 }
7497
7498 void
7499 ClearEngineOutputPane (int which)
7500 {
7501     static FrontEndProgramStats dummyStats;
7502     dummyStats.which = which;
7503     dummyStats.pv = "#";
7504     SetProgramStats( &dummyStats );
7505 }
7506
7507 #define MAXPLAYERS 500
7508
7509 char *
7510 TourneyStandings (int display)
7511 {
7512     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7513     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7514     char result, *p, *names[MAXPLAYERS];
7515
7516     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7517         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7518     names[0] = p = strdup(appData.participants);
7519     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7520
7521     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7522
7523     while(result = appData.results[nr]) {
7524         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7525         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7526         wScore = bScore = 0;
7527         switch(result) {
7528           case '+': wScore = 2; break;
7529           case '-': bScore = 2; break;
7530           case '=': wScore = bScore = 1; break;
7531           case ' ':
7532           case '*': return strdup("busy"); // tourney not finished
7533         }
7534         score[w] += wScore;
7535         score[b] += bScore;
7536         games[w]++;
7537         games[b]++;
7538         nr++;
7539     }
7540     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7541     for(w=0; w<nPlayers; w++) {
7542         bScore = -1;
7543         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7544         ranking[w] = b; points[w] = bScore; score[b] = -2;
7545     }
7546     p = malloc(nPlayers*34+1);
7547     for(w=0; w<nPlayers && w<display; w++)
7548         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7549     free(names[0]);
7550     return p;
7551 }
7552
7553 void
7554 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7555 {       // count all piece types
7556         int p, f, r;
7557         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7558         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7559         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7560                 p = board[r][f];
7561                 pCnt[p]++;
7562                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7563                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7564                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7565                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7566                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7567                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7568         }
7569 }
7570
7571 int
7572 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7573 {
7574         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7575         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7576
7577         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7578         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7579         if(myPawns == 2 && nMine == 3) // KPP
7580             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7581         if(myPawns == 1 && nMine == 2) // KP
7582             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7583         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7584             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7585         if(myPawns) return FALSE;
7586         if(pCnt[WhiteRook+side])
7587             return pCnt[BlackRook-side] ||
7588                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7589                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7590                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7591         if(pCnt[WhiteCannon+side]) {
7592             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7593             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7594         }
7595         if(pCnt[WhiteKnight+side])
7596             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7597         return FALSE;
7598 }
7599
7600 int
7601 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7602 {
7603         VariantClass v = gameInfo.variant;
7604
7605         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7606         if(v == VariantShatranj) return TRUE; // always winnable through baring
7607         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7608         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7609
7610         if(v == VariantXiangqi) {
7611                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7612
7613                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7614                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7615                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7616                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7617                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7618                 if(stale) // we have at least one last-rank P plus perhaps C
7619                     return majors // KPKX
7620                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7621                 else // KCA*E*
7622                     return pCnt[WhiteFerz+side] // KCAK
7623                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7624                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7625                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7626
7627         } else if(v == VariantKnightmate) {
7628                 if(nMine == 1) return FALSE;
7629                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7630         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7631                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7632
7633                 if(nMine == 1) return FALSE; // bare King
7634                 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
7635                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7636                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7637                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7638                 if(pCnt[WhiteKnight+side])
7639                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7640                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7641                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7642                 if(nBishops)
7643                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7644                 if(pCnt[WhiteAlfil+side])
7645                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7646                 if(pCnt[WhiteWazir+side])
7647                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7648         }
7649
7650         return TRUE;
7651 }
7652
7653 int
7654 CompareWithRights (Board b1, Board b2)
7655 {
7656     int rights = 0;
7657     if(!CompareBoards(b1, b2)) return FALSE;
7658     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7659     /* compare castling rights */
7660     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7661            rights++; /* King lost rights, while rook still had them */
7662     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7663         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7664            rights++; /* but at least one rook lost them */
7665     }
7666     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7667            rights++;
7668     if( b1[CASTLING][5] != NoRights ) {
7669         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7670            rights++;
7671     }
7672     return rights == 0;
7673 }
7674
7675 int
7676 Adjudicate (ChessProgramState *cps)
7677 {       // [HGM] some adjudications useful with buggy engines
7678         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7679         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7680         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7681         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7682         int k, drop, count = 0; static int bare = 1;
7683         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7684         Boolean canAdjudicate = !appData.icsActive;
7685
7686         // most tests only when we understand the game, i.e. legality-checking on
7687             if( appData.testLegality )
7688             {   /* [HGM] Some more adjudications for obstinate engines */
7689                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7690                 static int moveCount = 6;
7691                 ChessMove result;
7692                 char *reason = NULL;
7693
7694                 /* Count what is on board. */
7695                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7696
7697                 /* Some material-based adjudications that have to be made before stalemate test */
7698                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7699                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7700                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7701                      if(canAdjudicate && appData.checkMates) {
7702                          if(engineOpponent)
7703                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7704                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7705                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7706                          return 1;
7707                      }
7708                 }
7709
7710                 /* Bare King in Shatranj (loses) or Losers (wins) */
7711                 if( nrW == 1 || nrB == 1) {
7712                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7713                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7714                      if(canAdjudicate && appData.checkMates) {
7715                          if(engineOpponent)
7716                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7717                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7718                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7719                          return 1;
7720                      }
7721                   } else
7722                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7723                   {    /* bare King */
7724                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7725                         if(canAdjudicate && appData.checkMates) {
7726                             /* but only adjudicate if adjudication enabled */
7727                             if(engineOpponent)
7728                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7729                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7730                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7731                             return 1;
7732                         }
7733                   }
7734                 } else bare = 1;
7735
7736
7737             // don't wait for engine to announce game end if we can judge ourselves
7738             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7739               case MT_CHECK:
7740                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7741                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7742                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7743                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7744                             checkCnt++;
7745                         if(checkCnt >= 2) {
7746                             reason = "Xboard adjudication: 3rd check";
7747                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7748                             break;
7749                         }
7750                     }
7751                 }
7752               case MT_NONE:
7753               default:
7754                 break;
7755               case MT_STALEMATE:
7756               case MT_STAINMATE:
7757                 reason = "Xboard adjudication: Stalemate";
7758                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7759                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7760                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7761                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7762                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7763                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7764                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7765                                                                         EP_CHECKMATE : EP_WINS);
7766                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7767                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7768                 }
7769                 break;
7770               case MT_CHECKMATE:
7771                 reason = "Xboard adjudication: Checkmate";
7772                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7773                 if(gameInfo.variant == VariantShogi) {
7774                     if(forwardMostMove > backwardMostMove
7775                        && moveList[forwardMostMove-1][1] == '@'
7776                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7777                         reason = "XBoard adjudication: pawn-drop mate";
7778                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7779                     }
7780                 }
7781                 break;
7782             }
7783
7784                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7785                     case EP_STALEMATE:
7786                         result = GameIsDrawn; break;
7787                     case EP_CHECKMATE:
7788                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7789                     case EP_WINS:
7790                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7791                     default:
7792                         result = EndOfFile;
7793                 }
7794                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7795                     if(engineOpponent)
7796                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7797                     GameEnds( result, reason, GE_XBOARD );
7798                     return 1;
7799                 }
7800
7801                 /* Next absolutely insufficient mating material. */
7802                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7803                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7804                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7805
7806                      /* always flag draws, for judging claims */
7807                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7808
7809                      if(canAdjudicate && appData.materialDraws) {
7810                          /* but only adjudicate them if adjudication enabled */
7811                          if(engineOpponent) {
7812                            SendToProgram("force\n", engineOpponent); // suppress reply
7813                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7814                          }
7815                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7816                          return 1;
7817                      }
7818                 }
7819
7820                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7821                 if(gameInfo.variant == VariantXiangqi ?
7822                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7823                  : nrW + nrB == 4 &&
7824                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7825                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7826                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7827                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7828                    ) ) {
7829                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7830                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7831                           if(engineOpponent) {
7832                             SendToProgram("force\n", engineOpponent); // suppress reply
7833                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7834                           }
7835                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7836                           return 1;
7837                      }
7838                 } else moveCount = 6;
7839             }
7840
7841         // Repetition draws and 50-move rule can be applied independently of legality testing
7842
7843                 /* Check for rep-draws */
7844                 count = 0;
7845                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7846                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7847                 for(k = forwardMostMove-2;
7848                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7849                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7850                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7851                     k-=2)
7852                 {   int rights=0;
7853                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7854                         /* compare castling rights */
7855                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7856                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7857                                 rights++; /* King lost rights, while rook still had them */
7858                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7859                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7860                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7861                                    rights++; /* but at least one rook lost them */
7862                         }
7863                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7864                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7865                                 rights++;
7866                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7867                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7868                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7869                                    rights++;
7870                         }
7871                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7872                             && appData.drawRepeats > 1) {
7873                              /* adjudicate after user-specified nr of repeats */
7874                              int result = GameIsDrawn;
7875                              char *details = "XBoard adjudication: repetition draw";
7876                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7877                                 // [HGM] xiangqi: check for forbidden perpetuals
7878                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7879                                 for(m=forwardMostMove; m>k; m-=2) {
7880                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7881                                         ourPerpetual = 0; // the current mover did not always check
7882                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7883                                         hisPerpetual = 0; // the opponent did not always check
7884                                 }
7885                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7886                                                                         ourPerpetual, hisPerpetual);
7887                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7888                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7889                                     details = "Xboard adjudication: perpetual checking";
7890                                 } else
7891                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7892                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7893                                 } else
7894                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7895                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7896                                         result = BlackWins;
7897                                         details = "Xboard adjudication: repetition";
7898                                     }
7899                                 } else // it must be XQ
7900                                 // Now check for perpetual chases
7901                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7902                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7903                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7904                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7905                                         static char resdet[MSG_SIZ];
7906                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7907                                         details = resdet;
7908                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7909                                     } else
7910                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7911                                         break; // Abort repetition-checking loop.
7912                                 }
7913                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7914                              }
7915                              if(engineOpponent) {
7916                                SendToProgram("force\n", engineOpponent); // suppress reply
7917                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7918                              }
7919                              GameEnds( result, details, GE_XBOARD );
7920                              return 1;
7921                         }
7922                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7923                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7924                     }
7925                 }
7926
7927                 /* Now we test for 50-move draws. Determine ply count */
7928                 count = forwardMostMove;
7929                 /* look for last irreversble move */
7930                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7931                     count--;
7932                 /* if we hit starting position, add initial plies */
7933                 if( count == backwardMostMove )
7934                     count -= initialRulePlies;
7935                 count = forwardMostMove - count;
7936                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7937                         // adjust reversible move counter for checks in Xiangqi
7938                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7939                         if(i < backwardMostMove) i = backwardMostMove;
7940                         while(i <= forwardMostMove) {
7941                                 lastCheck = inCheck; // check evasion does not count
7942                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7943                                 if(inCheck || lastCheck) count--; // check does not count
7944                                 i++;
7945                         }
7946                 }
7947                 if( count >= 100)
7948                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7949                          /* this is used to judge if draw claims are legal */
7950                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7951                          if(engineOpponent) {
7952                            SendToProgram("force\n", engineOpponent); // suppress reply
7953                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7954                          }
7955                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7956                          return 1;
7957                 }
7958
7959                 /* if draw offer is pending, treat it as a draw claim
7960                  * when draw condition present, to allow engines a way to
7961                  * claim draws before making their move to avoid a race
7962                  * condition occurring after their move
7963                  */
7964                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7965                          char *p = NULL;
7966                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7967                              p = "Draw claim: 50-move rule";
7968                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7969                              p = "Draw claim: 3-fold repetition";
7970                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7971                              p = "Draw claim: insufficient mating material";
7972                          if( p != NULL && canAdjudicate) {
7973                              if(engineOpponent) {
7974                                SendToProgram("force\n", engineOpponent); // suppress reply
7975                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7976                              }
7977                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7978                              return 1;
7979                          }
7980                 }
7981
7982                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7983                     if(engineOpponent) {
7984                       SendToProgram("force\n", engineOpponent); // suppress reply
7985                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7986                     }
7987                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7988                     return 1;
7989                 }
7990         return 0;
7991 }
7992
7993 char *
7994 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7995 {   // [HGM] book: this routine intercepts moves to simulate book replies
7996     char *bookHit = NULL;
7997
7998     //first determine if the incoming move brings opponent into his book
7999     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8000         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8001     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8002     if(bookHit != NULL && !cps->bookSuspend) {
8003         // make sure opponent is not going to reply after receiving move to book position
8004         SendToProgram("force\n", cps);
8005         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8006     }
8007     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8008     // now arrange restart after book miss
8009     if(bookHit) {
8010         // after a book hit we never send 'go', and the code after the call to this routine
8011         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8012         char buf[MSG_SIZ], *move = bookHit;
8013         if(cps->useSAN) {
8014             int fromX, fromY, toX, toY;
8015             char promoChar;
8016             ChessMove moveType;
8017             move = buf + 30;
8018             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8019                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8020                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8021                                     PosFlags(forwardMostMove),
8022                                     fromY, fromX, toY, toX, promoChar, move);
8023             } else {
8024                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8025                 bookHit = NULL;
8026             }
8027         }
8028         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8029         SendToProgram(buf, cps);
8030         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8031     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8032         SendToProgram("go\n", cps);
8033         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8034     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8035         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8036             SendToProgram("go\n", cps);
8037         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8038     }
8039     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8040 }
8041
8042 int
8043 LoadError (char *errmess, ChessProgramState *cps)
8044 {   // unloads engine and switches back to -ncp mode if it was first
8045     if(cps->initDone) return FALSE;
8046     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8047     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8048     cps->pr = NoProc;
8049     if(cps == &first) {
8050         appData.noChessProgram = TRUE;
8051         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8052         gameMode = BeginningOfGame; ModeHighlight();
8053         SetNCPMode();
8054     }
8055     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8056     DisplayMessage("", ""); // erase waiting message
8057     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8058     return TRUE;
8059 }
8060
8061 char *savedMessage;
8062 ChessProgramState *savedState;
8063 void
8064 DeferredBookMove (void)
8065 {
8066         if(savedState->lastPing != savedState->lastPong)
8067                     ScheduleDelayedEvent(DeferredBookMove, 10);
8068         else
8069         HandleMachineMove(savedMessage, savedState);
8070 }
8071
8072 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8073 static ChessProgramState *stalledEngine;
8074 static char stashedInputMove[MSG_SIZ];
8075
8076 void
8077 HandleMachineMove (char *message, ChessProgramState *cps)
8078 {
8079     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8080     char realname[MSG_SIZ];
8081     int fromX, fromY, toX, toY;
8082     ChessMove moveType;
8083     char promoChar;
8084     char *p, *pv=buf1;
8085     int machineWhite, oldError;
8086     char *bookHit;
8087
8088     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8089         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8090         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8091             DisplayError(_("Invalid pairing from pairing engine"), 0);
8092             return;
8093         }
8094         pairingReceived = 1;
8095         NextMatchGame();
8096         return; // Skim the pairing messages here.
8097     }
8098
8099     oldError = cps->userError; cps->userError = 0;
8100
8101 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8102     /*
8103      * Kludge to ignore BEL characters
8104      */
8105     while (*message == '\007') message++;
8106
8107     /*
8108      * [HGM] engine debug message: ignore lines starting with '#' character
8109      */
8110     if(cps->debug && *message == '#') return;
8111
8112     /*
8113      * Look for book output
8114      */
8115     if (cps == &first && bookRequested) {
8116         if (message[0] == '\t' || message[0] == ' ') {
8117             /* Part of the book output is here; append it */
8118             strcat(bookOutput, message);
8119             strcat(bookOutput, "  \n");
8120             return;
8121         } else if (bookOutput[0] != NULLCHAR) {
8122             /* All of book output has arrived; display it */
8123             char *p = bookOutput;
8124             while (*p != NULLCHAR) {
8125                 if (*p == '\t') *p = ' ';
8126                 p++;
8127             }
8128             DisplayInformation(bookOutput);
8129             bookRequested = FALSE;
8130             /* Fall through to parse the current output */
8131         }
8132     }
8133
8134     /*
8135      * Look for machine move.
8136      */
8137     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8138         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8139     {
8140         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8141             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8142             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8143             stalledEngine = cps;
8144             if(appData.ponderNextMove) { // bring opponent out of ponder
8145                 if(gameMode == TwoMachinesPlay) {
8146                     if(cps->other->pause)
8147                         PauseEngine(cps->other);
8148                     else
8149                         SendToProgram("easy\n", cps->other);
8150                 }
8151             }
8152             StopClocks();
8153             return;
8154         }
8155
8156         /* This method is only useful on engines that support ping */
8157         if (cps->lastPing != cps->lastPong) {
8158           if (gameMode == BeginningOfGame) {
8159             /* Extra move from before last new; ignore */
8160             if (appData.debugMode) {
8161                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8162             }
8163           } else {
8164             if (appData.debugMode) {
8165                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8166                         cps->which, gameMode);
8167             }
8168
8169             SendToProgram("undo\n", cps);
8170           }
8171           return;
8172         }
8173
8174         switch (gameMode) {
8175           case BeginningOfGame:
8176             /* Extra move from before last reset; ignore */
8177             if (appData.debugMode) {
8178                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8179             }
8180             return;
8181
8182           case EndOfGame:
8183           case IcsIdle:
8184           default:
8185             /* Extra move after we tried to stop.  The mode test is
8186                not a reliable way of detecting this problem, but it's
8187                the best we can do on engines that don't support ping.
8188             */
8189             if (appData.debugMode) {
8190                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8191                         cps->which, gameMode);
8192             }
8193             SendToProgram("undo\n", cps);
8194             return;
8195
8196           case MachinePlaysWhite:
8197           case IcsPlayingWhite:
8198             machineWhite = TRUE;
8199             break;
8200
8201           case MachinePlaysBlack:
8202           case IcsPlayingBlack:
8203             machineWhite = FALSE;
8204             break;
8205
8206           case TwoMachinesPlay:
8207             machineWhite = (cps->twoMachinesColor[0] == 'w');
8208             break;
8209         }
8210         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8211             if (appData.debugMode) {
8212                 fprintf(debugFP,
8213                         "Ignoring move out of turn by %s, gameMode %d"
8214                         ", forwardMost %d\n",
8215                         cps->which, gameMode, forwardMostMove);
8216             }
8217             return;
8218         }
8219
8220         if(cps->alphaRank) AlphaRank(machineMove, 4);
8221         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8222                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8223             /* Machine move could not be parsed; ignore it. */
8224           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8225                     machineMove, _(cps->which));
8226             DisplayMoveError(buf1);
8227             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8228                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8229             if (gameMode == TwoMachinesPlay) {
8230               GameEnds(machineWhite ? BlackWins : WhiteWins,
8231                        buf1, GE_XBOARD);
8232             }
8233             return;
8234         }
8235
8236         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8237         /* So we have to redo legality test with true e.p. status here,  */
8238         /* to make sure an illegal e.p. capture does not slip through,   */
8239         /* to cause a forfeit on a justified illegal-move complaint      */
8240         /* of the opponent.                                              */
8241         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8242            ChessMove moveType;
8243            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8244                              fromY, fromX, toY, toX, promoChar);
8245             if(moveType == IllegalMove) {
8246               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8247                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8248                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8249                            buf1, GE_XBOARD);
8250                 return;
8251            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8252            /* [HGM] Kludge to handle engines that send FRC-style castling
8253               when they shouldn't (like TSCP-Gothic) */
8254            switch(moveType) {
8255              case WhiteASideCastleFR:
8256              case BlackASideCastleFR:
8257                toX+=2;
8258                currentMoveString[2]++;
8259                break;
8260              case WhiteHSideCastleFR:
8261              case BlackHSideCastleFR:
8262                toX--;
8263                currentMoveString[2]--;
8264                break;
8265              default: ; // nothing to do, but suppresses warning of pedantic compilers
8266            }
8267         }
8268         hintRequested = FALSE;
8269         lastHint[0] = NULLCHAR;
8270         bookRequested = FALSE;
8271         /* Program may be pondering now */
8272         cps->maybeThinking = TRUE;
8273         if (cps->sendTime == 2) cps->sendTime = 1;
8274         if (cps->offeredDraw) cps->offeredDraw--;
8275
8276         /* [AS] Save move info*/
8277         pvInfoList[ forwardMostMove ].score = programStats.score;
8278         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8279         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8280
8281         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8282
8283         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8284         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8285             int count = 0;
8286
8287             while( count < adjudicateLossPlies ) {
8288                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8289
8290                 if( count & 1 ) {
8291                     score = -score; /* Flip score for winning side */
8292                 }
8293
8294                 if( score > adjudicateLossThreshold ) {
8295                     break;
8296                 }
8297
8298                 count++;
8299             }
8300
8301             if( count >= adjudicateLossPlies ) {
8302                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8303
8304                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8305                     "Xboard adjudication",
8306                     GE_XBOARD );
8307
8308                 return;
8309             }
8310         }
8311
8312         if(Adjudicate(cps)) {
8313             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8314             return; // [HGM] adjudicate: for all automatic game ends
8315         }
8316
8317 #if ZIPPY
8318         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8319             first.initDone) {
8320           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8321                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8322                 SendToICS("draw ");
8323                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8324           }
8325           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8326           ics_user_moved = 1;
8327           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8328                 char buf[3*MSG_SIZ];
8329
8330                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8331                         programStats.score / 100.,
8332                         programStats.depth,
8333                         programStats.time / 100.,
8334                         (unsigned int)programStats.nodes,
8335                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8336                         programStats.movelist);
8337                 SendToICS(buf);
8338 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8339           }
8340         }
8341 #endif
8342
8343         /* [AS] Clear stats for next move */
8344         ClearProgramStats();
8345         thinkOutput[0] = NULLCHAR;
8346         hiddenThinkOutputState = 0;
8347
8348         bookHit = NULL;
8349         if (gameMode == TwoMachinesPlay) {
8350             /* [HGM] relaying draw offers moved to after reception of move */
8351             /* and interpreting offer as claim if it brings draw condition */
8352             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8353                 SendToProgram("draw\n", cps->other);
8354             }
8355             if (cps->other->sendTime) {
8356                 SendTimeRemaining(cps->other,
8357                                   cps->other->twoMachinesColor[0] == 'w');
8358             }
8359             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8360             if (firstMove && !bookHit) {
8361                 firstMove = FALSE;
8362                 if (cps->other->useColors) {
8363                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8364                 }
8365                 SendToProgram("go\n", cps->other);
8366             }
8367             cps->other->maybeThinking = TRUE;
8368         }
8369
8370         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8371
8372         if (!pausing && appData.ringBellAfterMoves) {
8373             RingBell();
8374         }
8375
8376         /*
8377          * Reenable menu items that were disabled while
8378          * machine was thinking
8379          */
8380         if (gameMode != TwoMachinesPlay)
8381             SetUserThinkingEnables();
8382
8383         // [HGM] book: after book hit opponent has received move and is now in force mode
8384         // force the book reply into it, and then fake that it outputted this move by jumping
8385         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8386         if(bookHit) {
8387                 static char bookMove[MSG_SIZ]; // a bit generous?
8388
8389                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8390                 strcat(bookMove, bookHit);
8391                 message = bookMove;
8392                 cps = cps->other;
8393                 programStats.nodes = programStats.depth = programStats.time =
8394                 programStats.score = programStats.got_only_move = 0;
8395                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8396
8397                 if(cps->lastPing != cps->lastPong) {
8398                     savedMessage = message; // args for deferred call
8399                     savedState = cps;
8400                     ScheduleDelayedEvent(DeferredBookMove, 10);
8401                     return;
8402                 }
8403                 goto FakeBookMove;
8404         }
8405
8406         return;
8407     }
8408
8409     /* Set special modes for chess engines.  Later something general
8410      *  could be added here; for now there is just one kludge feature,
8411      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8412      *  when "xboard" is given as an interactive command.
8413      */
8414     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8415         cps->useSigint = FALSE;
8416         cps->useSigterm = FALSE;
8417     }
8418     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8419       ParseFeatures(message+8, cps);
8420       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8421     }
8422
8423     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8424                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8425       int dummy, s=6; char buf[MSG_SIZ];
8426       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8427       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8428       if(startedFromSetupPosition) return;
8429       ParseFEN(boards[0], &dummy, message+s);
8430       DrawPosition(TRUE, boards[0]);
8431       startedFromSetupPosition = TRUE;
8432       return;
8433     }
8434     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8435      * want this, I was asked to put it in, and obliged.
8436      */
8437     if (!strncmp(message, "setboard ", 9)) {
8438         Board initial_position;
8439
8440         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8441
8442         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8443             DisplayError(_("Bad FEN received from engine"), 0);
8444             return ;
8445         } else {
8446            Reset(TRUE, FALSE);
8447            CopyBoard(boards[0], initial_position);
8448            initialRulePlies = FENrulePlies;
8449            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8450            else gameMode = MachinePlaysBlack;
8451            DrawPosition(FALSE, boards[currentMove]);
8452         }
8453         return;
8454     }
8455
8456     /*
8457      * Look for communication commands
8458      */
8459     if (!strncmp(message, "telluser ", 9)) {
8460         if(message[9] == '\\' && message[10] == '\\')
8461             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8462         PlayTellSound();
8463         DisplayNote(message + 9);
8464         return;
8465     }
8466     if (!strncmp(message, "tellusererror ", 14)) {
8467         cps->userError = 1;
8468         if(message[14] == '\\' && message[15] == '\\')
8469             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8470         PlayTellSound();
8471         DisplayError(message + 14, 0);
8472         return;
8473     }
8474     if (!strncmp(message, "tellopponent ", 13)) {
8475       if (appData.icsActive) {
8476         if (loggedOn) {
8477           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8478           SendToICS(buf1);
8479         }
8480       } else {
8481         DisplayNote(message + 13);
8482       }
8483       return;
8484     }
8485     if (!strncmp(message, "tellothers ", 11)) {
8486       if (appData.icsActive) {
8487         if (loggedOn) {
8488           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8489           SendToICS(buf1);
8490         }
8491       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8492       return;
8493     }
8494     if (!strncmp(message, "tellall ", 8)) {
8495       if (appData.icsActive) {
8496         if (loggedOn) {
8497           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8498           SendToICS(buf1);
8499         }
8500       } else {
8501         DisplayNote(message + 8);
8502       }
8503       return;
8504     }
8505     if (strncmp(message, "warning", 7) == 0) {
8506         /* Undocumented feature, use tellusererror in new code */
8507         DisplayError(message, 0);
8508         return;
8509     }
8510     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8511         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8512         strcat(realname, " query");
8513         AskQuestion(realname, buf2, buf1, cps->pr);
8514         return;
8515     }
8516     /* Commands from the engine directly to ICS.  We don't allow these to be
8517      *  sent until we are logged on. Crafty kibitzes have been known to
8518      *  interfere with the login process.
8519      */
8520     if (loggedOn) {
8521         if (!strncmp(message, "tellics ", 8)) {
8522             SendToICS(message + 8);
8523             SendToICS("\n");
8524             return;
8525         }
8526         if (!strncmp(message, "tellicsnoalias ", 15)) {
8527             SendToICS(ics_prefix);
8528             SendToICS(message + 15);
8529             SendToICS("\n");
8530             return;
8531         }
8532         /* The following are for backward compatibility only */
8533         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8534             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8535             SendToICS(ics_prefix);
8536             SendToICS(message);
8537             SendToICS("\n");
8538             return;
8539         }
8540     }
8541     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8542         return;
8543     }
8544     /*
8545      * If the move is illegal, cancel it and redraw the board.
8546      * Also deal with other error cases.  Matching is rather loose
8547      * here to accommodate engines written before the spec.
8548      */
8549     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8550         strncmp(message, "Error", 5) == 0) {
8551         if (StrStr(message, "name") ||
8552             StrStr(message, "rating") || StrStr(message, "?") ||
8553             StrStr(message, "result") || StrStr(message, "board") ||
8554             StrStr(message, "bk") || StrStr(message, "computer") ||
8555             StrStr(message, "variant") || StrStr(message, "hint") ||
8556             StrStr(message, "random") || StrStr(message, "depth") ||
8557             StrStr(message, "accepted")) {
8558             return;
8559         }
8560         if (StrStr(message, "protover")) {
8561           /* Program is responding to input, so it's apparently done
8562              initializing, and this error message indicates it is
8563              protocol version 1.  So we don't need to wait any longer
8564              for it to initialize and send feature commands. */
8565           FeatureDone(cps, 1);
8566           cps->protocolVersion = 1;
8567           return;
8568         }
8569         cps->maybeThinking = FALSE;
8570
8571         if (StrStr(message, "draw")) {
8572             /* Program doesn't have "draw" command */
8573             cps->sendDrawOffers = 0;
8574             return;
8575         }
8576         if (cps->sendTime != 1 &&
8577             (StrStr(message, "time") || StrStr(message, "otim"))) {
8578           /* Program apparently doesn't have "time" or "otim" command */
8579           cps->sendTime = 0;
8580           return;
8581         }
8582         if (StrStr(message, "analyze")) {
8583             cps->analysisSupport = FALSE;
8584             cps->analyzing = FALSE;
8585 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8586             EditGameEvent(); // [HGM] try to preserve loaded game
8587             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8588             DisplayError(buf2, 0);
8589             return;
8590         }
8591         if (StrStr(message, "(no matching move)st")) {
8592           /* Special kludge for GNU Chess 4 only */
8593           cps->stKludge = TRUE;
8594           SendTimeControl(cps, movesPerSession, timeControl,
8595                           timeIncrement, appData.searchDepth,
8596                           searchTime);
8597           return;
8598         }
8599         if (StrStr(message, "(no matching move)sd")) {
8600           /* Special kludge for GNU Chess 4 only */
8601           cps->sdKludge = TRUE;
8602           SendTimeControl(cps, movesPerSession, timeControl,
8603                           timeIncrement, appData.searchDepth,
8604                           searchTime);
8605           return;
8606         }
8607         if (!StrStr(message, "llegal")) {
8608             return;
8609         }
8610         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8611             gameMode == IcsIdle) return;
8612         if (forwardMostMove <= backwardMostMove) return;
8613         if (pausing) PauseEvent();
8614       if(appData.forceIllegal) {
8615             // [HGM] illegal: machine refused move; force position after move into it
8616           SendToProgram("force\n", cps);
8617           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8618                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8619                 // when black is to move, while there might be nothing on a2 or black
8620                 // might already have the move. So send the board as if white has the move.
8621                 // But first we must change the stm of the engine, as it refused the last move
8622                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8623                 if(WhiteOnMove(forwardMostMove)) {
8624                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8625                     SendBoard(cps, forwardMostMove); // kludgeless board
8626                 } else {
8627                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8628                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8629                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8630                 }
8631           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8632             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8633                  gameMode == TwoMachinesPlay)
8634               SendToProgram("go\n", cps);
8635             return;
8636       } else
8637         if (gameMode == PlayFromGameFile) {
8638             /* Stop reading this game file */
8639             gameMode = EditGame;
8640             ModeHighlight();
8641         }
8642         /* [HGM] illegal-move claim should forfeit game when Xboard */
8643         /* only passes fully legal moves                            */
8644         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8645             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8646                                 "False illegal-move claim", GE_XBOARD );
8647             return; // do not take back move we tested as valid
8648         }
8649         currentMove = forwardMostMove-1;
8650         DisplayMove(currentMove-1); /* before DisplayMoveError */
8651         SwitchClocks(forwardMostMove-1); // [HGM] race
8652         DisplayBothClocks();
8653         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8654                 parseList[currentMove], _(cps->which));
8655         DisplayMoveError(buf1);
8656         DrawPosition(FALSE, boards[currentMove]);
8657
8658         SetUserThinkingEnables();
8659         return;
8660     }
8661     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8662         /* Program has a broken "time" command that
8663            outputs a string not ending in newline.
8664            Don't use it. */
8665         cps->sendTime = 0;
8666     }
8667
8668     /*
8669      * If chess program startup fails, exit with an error message.
8670      * Attempts to recover here are futile. [HGM] Well, we try anyway
8671      */
8672     if ((StrStr(message, "unknown host") != NULL)
8673         || (StrStr(message, "No remote directory") != NULL)
8674         || (StrStr(message, "not found") != NULL)
8675         || (StrStr(message, "No such file") != NULL)
8676         || (StrStr(message, "can't alloc") != NULL)
8677         || (StrStr(message, "Permission denied") != NULL)) {
8678
8679         cps->maybeThinking = FALSE;
8680         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8681                 _(cps->which), cps->program, cps->host, message);
8682         RemoveInputSource(cps->isr);
8683         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8684             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8685             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8686         }
8687         return;
8688     }
8689
8690     /*
8691      * Look for hint output
8692      */
8693     if (sscanf(message, "Hint: %s", buf1) == 1) {
8694         if (cps == &first && hintRequested) {
8695             hintRequested = FALSE;
8696             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8697                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8698                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8699                                     PosFlags(forwardMostMove),
8700                                     fromY, fromX, toY, toX, promoChar, buf1);
8701                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8702                 DisplayInformation(buf2);
8703             } else {
8704                 /* Hint move could not be parsed!? */
8705               snprintf(buf2, sizeof(buf2),
8706                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8707                         buf1, _(cps->which));
8708                 DisplayError(buf2, 0);
8709             }
8710         } else {
8711           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8712         }
8713         return;
8714     }
8715
8716     /*
8717      * Ignore other messages if game is not in progress
8718      */
8719     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8720         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8721
8722     /*
8723      * look for win, lose, draw, or draw offer
8724      */
8725     if (strncmp(message, "1-0", 3) == 0) {
8726         char *p, *q, *r = "";
8727         p = strchr(message, '{');
8728         if (p) {
8729             q = strchr(p, '}');
8730             if (q) {
8731                 *q = NULLCHAR;
8732                 r = p + 1;
8733             }
8734         }
8735         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8736         return;
8737     } else if (strncmp(message, "0-1", 3) == 0) {
8738         char *p, *q, *r = "";
8739         p = strchr(message, '{');
8740         if (p) {
8741             q = strchr(p, '}');
8742             if (q) {
8743                 *q = NULLCHAR;
8744                 r = p + 1;
8745             }
8746         }
8747         /* Kludge for Arasan 4.1 bug */
8748         if (strcmp(r, "Black resigns") == 0) {
8749             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8750             return;
8751         }
8752         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8753         return;
8754     } else if (strncmp(message, "1/2", 3) == 0) {
8755         char *p, *q, *r = "";
8756         p = strchr(message, '{');
8757         if (p) {
8758             q = strchr(p, '}');
8759             if (q) {
8760                 *q = NULLCHAR;
8761                 r = p + 1;
8762             }
8763         }
8764
8765         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8766         return;
8767
8768     } else if (strncmp(message, "White resign", 12) == 0) {
8769         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8770         return;
8771     } else if (strncmp(message, "Black resign", 12) == 0) {
8772         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8773         return;
8774     } else if (strncmp(message, "White matches", 13) == 0 ||
8775                strncmp(message, "Black matches", 13) == 0   ) {
8776         /* [HGM] ignore GNUShogi noises */
8777         return;
8778     } else if (strncmp(message, "White", 5) == 0 &&
8779                message[5] != '(' &&
8780                StrStr(message, "Black") == NULL) {
8781         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8782         return;
8783     } else if (strncmp(message, "Black", 5) == 0 &&
8784                message[5] != '(') {
8785         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8786         return;
8787     } else if (strcmp(message, "resign") == 0 ||
8788                strcmp(message, "computer resigns") == 0) {
8789         switch (gameMode) {
8790           case MachinePlaysBlack:
8791           case IcsPlayingBlack:
8792             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8793             break;
8794           case MachinePlaysWhite:
8795           case IcsPlayingWhite:
8796             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8797             break;
8798           case TwoMachinesPlay:
8799             if (cps->twoMachinesColor[0] == 'w')
8800               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8801             else
8802               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8803             break;
8804           default:
8805             /* can't happen */
8806             break;
8807         }
8808         return;
8809     } else if (strncmp(message, "opponent mates", 14) == 0) {
8810         switch (gameMode) {
8811           case MachinePlaysBlack:
8812           case IcsPlayingBlack:
8813             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8814             break;
8815           case MachinePlaysWhite:
8816           case IcsPlayingWhite:
8817             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8818             break;
8819           case TwoMachinesPlay:
8820             if (cps->twoMachinesColor[0] == 'w')
8821               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8822             else
8823               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8824             break;
8825           default:
8826             /* can't happen */
8827             break;
8828         }
8829         return;
8830     } else if (strncmp(message, "computer mates", 14) == 0) {
8831         switch (gameMode) {
8832           case MachinePlaysBlack:
8833           case IcsPlayingBlack:
8834             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8835             break;
8836           case MachinePlaysWhite:
8837           case IcsPlayingWhite:
8838             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8839             break;
8840           case TwoMachinesPlay:
8841             if (cps->twoMachinesColor[0] == 'w')
8842               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8843             else
8844               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8845             break;
8846           default:
8847             /* can't happen */
8848             break;
8849         }
8850         return;
8851     } else if (strncmp(message, "checkmate", 9) == 0) {
8852         if (WhiteOnMove(forwardMostMove)) {
8853             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8854         } else {
8855             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8856         }
8857         return;
8858     } else if (strstr(message, "Draw") != NULL ||
8859                strstr(message, "game is a draw") != NULL) {
8860         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8861         return;
8862     } else if (strstr(message, "offer") != NULL &&
8863                strstr(message, "draw") != NULL) {
8864 #if ZIPPY
8865         if (appData.zippyPlay && first.initDone) {
8866             /* Relay offer to ICS */
8867             SendToICS(ics_prefix);
8868             SendToICS("draw\n");
8869         }
8870 #endif
8871         cps->offeredDraw = 2; /* valid until this engine moves twice */
8872         if (gameMode == TwoMachinesPlay) {
8873             if (cps->other->offeredDraw) {
8874                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8875             /* [HGM] in two-machine mode we delay relaying draw offer      */
8876             /* until after we also have move, to see if it is really claim */
8877             }
8878         } else if (gameMode == MachinePlaysWhite ||
8879                    gameMode == MachinePlaysBlack) {
8880           if (userOfferedDraw) {
8881             DisplayInformation(_("Machine accepts your draw offer"));
8882             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8883           } else {
8884             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8885           }
8886         }
8887     }
8888
8889
8890     /*
8891      * Look for thinking output
8892      */
8893     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8894           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8895                                 ) {
8896         int plylev, mvleft, mvtot, curscore, time;
8897         char mvname[MOVE_LEN];
8898         u64 nodes; // [DM]
8899         char plyext;
8900         int ignore = FALSE;
8901         int prefixHint = FALSE;
8902         mvname[0] = NULLCHAR;
8903
8904         switch (gameMode) {
8905           case MachinePlaysBlack:
8906           case IcsPlayingBlack:
8907             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8908             break;
8909           case MachinePlaysWhite:
8910           case IcsPlayingWhite:
8911             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8912             break;
8913           case AnalyzeMode:
8914           case AnalyzeFile:
8915             break;
8916           case IcsObserving: /* [DM] icsEngineAnalyze */
8917             if (!appData.icsEngineAnalyze) ignore = TRUE;
8918             break;
8919           case TwoMachinesPlay:
8920             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8921                 ignore = TRUE;
8922             }
8923             break;
8924           default:
8925             ignore = TRUE;
8926             break;
8927         }
8928
8929         if (!ignore) {
8930             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8931             buf1[0] = NULLCHAR;
8932             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8933                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8934
8935                 if (plyext != ' ' && plyext != '\t') {
8936                     time *= 100;
8937                 }
8938
8939                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8940                 if( cps->scoreIsAbsolute &&
8941                     ( gameMode == MachinePlaysBlack ||
8942                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8943                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8944                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8945                      !WhiteOnMove(currentMove)
8946                     ) )
8947                 {
8948                     curscore = -curscore;
8949                 }
8950
8951                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8952
8953                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8954                         char buf[MSG_SIZ];
8955                         FILE *f;
8956                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8957                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8958                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8959                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8960                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8961                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8962                                 fclose(f);
8963                         } else DisplayError(_("failed writing PV"), 0);
8964                 }
8965
8966                 tempStats.depth = plylev;
8967                 tempStats.nodes = nodes;
8968                 tempStats.time = time;
8969                 tempStats.score = curscore;
8970                 tempStats.got_only_move = 0;
8971
8972                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8973                         int ticklen;
8974
8975                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8976                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8977                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8978                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8979                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8980                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8981                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8982                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8983                 }
8984
8985                 /* Buffer overflow protection */
8986                 if (pv[0] != NULLCHAR) {
8987                     if (strlen(pv) >= sizeof(tempStats.movelist)
8988                         && appData.debugMode) {
8989                         fprintf(debugFP,
8990                                 "PV is too long; using the first %u bytes.\n",
8991                                 (unsigned) sizeof(tempStats.movelist) - 1);
8992                     }
8993
8994                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8995                 } else {
8996                     sprintf(tempStats.movelist, " no PV\n");
8997                 }
8998
8999                 if (tempStats.seen_stat) {
9000                     tempStats.ok_to_send = 1;
9001                 }
9002
9003                 if (strchr(tempStats.movelist, '(') != NULL) {
9004                     tempStats.line_is_book = 1;
9005                     tempStats.nr_moves = 0;
9006                     tempStats.moves_left = 0;
9007                 } else {
9008                     tempStats.line_is_book = 0;
9009                 }
9010
9011                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9012                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9013
9014                 SendProgramStatsToFrontend( cps, &tempStats );
9015
9016                 /*
9017                     [AS] Protect the thinkOutput buffer from overflow... this
9018                     is only useful if buf1 hasn't overflowed first!
9019                 */
9020                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9021                          plylev,
9022                          (gameMode == TwoMachinesPlay ?
9023                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9024                          ((double) curscore) / 100.0,
9025                          prefixHint ? lastHint : "",
9026                          prefixHint ? " " : "" );
9027
9028                 if( buf1[0] != NULLCHAR ) {
9029                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9030
9031                     if( strlen(pv) > max_len ) {
9032                         if( appData.debugMode) {
9033                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9034                         }
9035                         pv[max_len+1] = '\0';
9036                     }
9037
9038                     strcat( thinkOutput, pv);
9039                 }
9040
9041                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9042                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9043                     DisplayMove(currentMove - 1);
9044                 }
9045                 return;
9046
9047             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9048                 /* crafty (9.25+) says "(only move) <move>"
9049                  * if there is only 1 legal move
9050                  */
9051                 sscanf(p, "(only move) %s", buf1);
9052                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9053                 sprintf(programStats.movelist, "%s (only move)", buf1);
9054                 programStats.depth = 1;
9055                 programStats.nr_moves = 1;
9056                 programStats.moves_left = 1;
9057                 programStats.nodes = 1;
9058                 programStats.time = 1;
9059                 programStats.got_only_move = 1;
9060
9061                 /* Not really, but we also use this member to
9062                    mean "line isn't going to change" (Crafty
9063                    isn't searching, so stats won't change) */
9064                 programStats.line_is_book = 1;
9065
9066                 SendProgramStatsToFrontend( cps, &programStats );
9067
9068                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9069                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9070                     DisplayMove(currentMove - 1);
9071                 }
9072                 return;
9073             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9074                               &time, &nodes, &plylev, &mvleft,
9075                               &mvtot, mvname) >= 5) {
9076                 /* The stat01: line is from Crafty (9.29+) in response
9077                    to the "." command */
9078                 programStats.seen_stat = 1;
9079                 cps->maybeThinking = TRUE;
9080
9081                 if (programStats.got_only_move || !appData.periodicUpdates)
9082                   return;
9083
9084                 programStats.depth = plylev;
9085                 programStats.time = time;
9086                 programStats.nodes = nodes;
9087                 programStats.moves_left = mvleft;
9088                 programStats.nr_moves = mvtot;
9089                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9090                 programStats.ok_to_send = 1;
9091                 programStats.movelist[0] = '\0';
9092
9093                 SendProgramStatsToFrontend( cps, &programStats );
9094
9095                 return;
9096
9097             } else if (strncmp(message,"++",2) == 0) {
9098                 /* Crafty 9.29+ outputs this */
9099                 programStats.got_fail = 2;
9100                 return;
9101
9102             } else if (strncmp(message,"--",2) == 0) {
9103                 /* Crafty 9.29+ outputs this */
9104                 programStats.got_fail = 1;
9105                 return;
9106
9107             } else if (thinkOutput[0] != NULLCHAR &&
9108                        strncmp(message, "    ", 4) == 0) {
9109                 unsigned message_len;
9110
9111                 p = message;
9112                 while (*p && *p == ' ') p++;
9113
9114                 message_len = strlen( p );
9115
9116                 /* [AS] Avoid buffer overflow */
9117                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9118                     strcat(thinkOutput, " ");
9119                     strcat(thinkOutput, p);
9120                 }
9121
9122                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9123                     strcat(programStats.movelist, " ");
9124                     strcat(programStats.movelist, p);
9125                 }
9126
9127                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9128                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9129                     DisplayMove(currentMove - 1);
9130                 }
9131                 return;
9132             }
9133         }
9134         else {
9135             buf1[0] = NULLCHAR;
9136
9137             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9138                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9139             {
9140                 ChessProgramStats cpstats;
9141
9142                 if (plyext != ' ' && plyext != '\t') {
9143                     time *= 100;
9144                 }
9145
9146                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9147                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9148                     curscore = -curscore;
9149                 }
9150
9151                 cpstats.depth = plylev;
9152                 cpstats.nodes = nodes;
9153                 cpstats.time = time;
9154                 cpstats.score = curscore;
9155                 cpstats.got_only_move = 0;
9156                 cpstats.movelist[0] = '\0';
9157
9158                 if (buf1[0] != NULLCHAR) {
9159                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9160                 }
9161
9162                 cpstats.ok_to_send = 0;
9163                 cpstats.line_is_book = 0;
9164                 cpstats.nr_moves = 0;
9165                 cpstats.moves_left = 0;
9166
9167                 SendProgramStatsToFrontend( cps, &cpstats );
9168             }
9169         }
9170     }
9171 }
9172
9173
9174 /* Parse a game score from the character string "game", and
9175    record it as the history of the current game.  The game
9176    score is NOT assumed to start from the standard position.
9177    The display is not updated in any way.
9178    */
9179 void
9180 ParseGameHistory (char *game)
9181 {
9182     ChessMove moveType;
9183     int fromX, fromY, toX, toY, boardIndex;
9184     char promoChar;
9185     char *p, *q;
9186     char buf[MSG_SIZ];
9187
9188     if (appData.debugMode)
9189       fprintf(debugFP, "Parsing game history: %s\n", game);
9190
9191     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9192     gameInfo.site = StrSave(appData.icsHost);
9193     gameInfo.date = PGNDate();
9194     gameInfo.round = StrSave("-");
9195
9196     /* Parse out names of players */
9197     while (*game == ' ') game++;
9198     p = buf;
9199     while (*game != ' ') *p++ = *game++;
9200     *p = NULLCHAR;
9201     gameInfo.white = StrSave(buf);
9202     while (*game == ' ') game++;
9203     p = buf;
9204     while (*game != ' ' && *game != '\n') *p++ = *game++;
9205     *p = NULLCHAR;
9206     gameInfo.black = StrSave(buf);
9207
9208     /* Parse moves */
9209     boardIndex = blackPlaysFirst ? 1 : 0;
9210     yynewstr(game);
9211     for (;;) {
9212         yyboardindex = boardIndex;
9213         moveType = (ChessMove) Myylex();
9214         switch (moveType) {
9215           case IllegalMove:             /* maybe suicide chess, etc. */
9216   if (appData.debugMode) {
9217     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9218     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9219     setbuf(debugFP, NULL);
9220   }
9221           case WhitePromotion:
9222           case BlackPromotion:
9223           case WhiteNonPromotion:
9224           case BlackNonPromotion:
9225           case NormalMove:
9226           case WhiteCapturesEnPassant:
9227           case BlackCapturesEnPassant:
9228           case WhiteKingSideCastle:
9229           case WhiteQueenSideCastle:
9230           case BlackKingSideCastle:
9231           case BlackQueenSideCastle:
9232           case WhiteKingSideCastleWild:
9233           case WhiteQueenSideCastleWild:
9234           case BlackKingSideCastleWild:
9235           case BlackQueenSideCastleWild:
9236           /* PUSH Fabien */
9237           case WhiteHSideCastleFR:
9238           case WhiteASideCastleFR:
9239           case BlackHSideCastleFR:
9240           case BlackASideCastleFR:
9241           /* POP Fabien */
9242             fromX = currentMoveString[0] - AAA;
9243             fromY = currentMoveString[1] - ONE;
9244             toX = currentMoveString[2] - AAA;
9245             toY = currentMoveString[3] - ONE;
9246             promoChar = currentMoveString[4];
9247             break;
9248           case WhiteDrop:
9249           case BlackDrop:
9250             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9251             fromX = moveType == WhiteDrop ?
9252               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9253             (int) CharToPiece(ToLower(currentMoveString[0]));
9254             fromY = DROP_RANK;
9255             toX = currentMoveString[2] - AAA;
9256             toY = currentMoveString[3] - ONE;
9257             promoChar = NULLCHAR;
9258             break;
9259           case AmbiguousMove:
9260             /* bug? */
9261             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9262   if (appData.debugMode) {
9263     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9264     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9265     setbuf(debugFP, NULL);
9266   }
9267             DisplayError(buf, 0);
9268             return;
9269           case ImpossibleMove:
9270             /* bug? */
9271             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9272   if (appData.debugMode) {
9273     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9274     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9275     setbuf(debugFP, NULL);
9276   }
9277             DisplayError(buf, 0);
9278             return;
9279           case EndOfFile:
9280             if (boardIndex < backwardMostMove) {
9281                 /* Oops, gap.  How did that happen? */
9282                 DisplayError(_("Gap in move list"), 0);
9283                 return;
9284             }
9285             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9286             if (boardIndex > forwardMostMove) {
9287                 forwardMostMove = boardIndex;
9288             }
9289             return;
9290           case ElapsedTime:
9291             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9292                 strcat(parseList[boardIndex-1], " ");
9293                 strcat(parseList[boardIndex-1], yy_text);
9294             }
9295             continue;
9296           case Comment:
9297           case PGNTag:
9298           case NAG:
9299           default:
9300             /* ignore */
9301             continue;
9302           case WhiteWins:
9303           case BlackWins:
9304           case GameIsDrawn:
9305           case GameUnfinished:
9306             if (gameMode == IcsExamining) {
9307                 if (boardIndex < backwardMostMove) {
9308                     /* Oops, gap.  How did that happen? */
9309                     return;
9310                 }
9311                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9312                 return;
9313             }
9314             gameInfo.result = moveType;
9315             p = strchr(yy_text, '{');
9316             if (p == NULL) p = strchr(yy_text, '(');
9317             if (p == NULL) {
9318                 p = yy_text;
9319                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9320             } else {
9321                 q = strchr(p, *p == '{' ? '}' : ')');
9322                 if (q != NULL) *q = NULLCHAR;
9323                 p++;
9324             }
9325             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9326             gameInfo.resultDetails = StrSave(p);
9327             continue;
9328         }
9329         if (boardIndex >= forwardMostMove &&
9330             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9331             backwardMostMove = blackPlaysFirst ? 1 : 0;
9332             return;
9333         }
9334         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9335                                  fromY, fromX, toY, toX, promoChar,
9336                                  parseList[boardIndex]);
9337         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9338         /* currentMoveString is set as a side-effect of yylex */
9339         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9340         strcat(moveList[boardIndex], "\n");
9341         boardIndex++;
9342         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9343         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9344           case MT_NONE:
9345           case MT_STALEMATE:
9346           default:
9347             break;
9348           case MT_CHECK:
9349             if(gameInfo.variant != VariantShogi)
9350                 strcat(parseList[boardIndex - 1], "+");
9351             break;
9352           case MT_CHECKMATE:
9353           case MT_STAINMATE:
9354             strcat(parseList[boardIndex - 1], "#");
9355             break;
9356         }
9357     }
9358 }
9359
9360
9361 /* Apply a move to the given board  */
9362 void
9363 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9364 {
9365   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9366   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9367
9368     /* [HGM] compute & store e.p. status and castling rights for new position */
9369     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9370
9371       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9372       oldEP = (signed char)board[EP_STATUS];
9373       board[EP_STATUS] = EP_NONE;
9374
9375   if (fromY == DROP_RANK) {
9376         /* must be first */
9377         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9378             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9379             return;
9380         }
9381         piece = board[toY][toX] = (ChessSquare) fromX;
9382   } else {
9383       int i;
9384
9385       if( board[toY][toX] != EmptySquare )
9386            board[EP_STATUS] = EP_CAPTURE;
9387
9388       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9389            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9390                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9391       } else
9392       if( board[fromY][fromX] == WhitePawn ) {
9393            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9394                board[EP_STATUS] = EP_PAWN_MOVE;
9395            if( toY-fromY==2) {
9396                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9397                         gameInfo.variant != VariantBerolina || toX < fromX)
9398                       board[EP_STATUS] = toX | berolina;
9399                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9400                         gameInfo.variant != VariantBerolina || toX > fromX)
9401                       board[EP_STATUS] = toX;
9402            }
9403       } else
9404       if( board[fromY][fromX] == BlackPawn ) {
9405            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9406                board[EP_STATUS] = EP_PAWN_MOVE;
9407            if( toY-fromY== -2) {
9408                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9409                         gameInfo.variant != VariantBerolina || toX < fromX)
9410                       board[EP_STATUS] = toX | berolina;
9411                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9412                         gameInfo.variant != VariantBerolina || toX > fromX)
9413                       board[EP_STATUS] = toX;
9414            }
9415        }
9416
9417        for(i=0; i<nrCastlingRights; i++) {
9418            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9419               board[CASTLING][i] == toX   && castlingRank[i] == toY
9420              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9421        }
9422
9423        if(gameInfo.variant == VariantSChess) { // update virginity
9424            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9425            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9426            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9427            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9428        }
9429
9430      if (fromX == toX && fromY == toY) return;
9431
9432      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9433      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9434      if(gameInfo.variant == VariantKnightmate)
9435          king += (int) WhiteUnicorn - (int) WhiteKing;
9436
9437     /* Code added by Tord: */
9438     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9439     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9440         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9441       board[fromY][fromX] = EmptySquare;
9442       board[toY][toX] = EmptySquare;
9443       if((toX > fromX) != (piece == WhiteRook)) {
9444         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9445       } else {
9446         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9447       }
9448     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9449                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9450       board[fromY][fromX] = EmptySquare;
9451       board[toY][toX] = EmptySquare;
9452       if((toX > fromX) != (piece == BlackRook)) {
9453         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9454       } else {
9455         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9456       }
9457     /* End of code added by Tord */
9458
9459     } else if (board[fromY][fromX] == king
9460         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9461         && toY == fromY && toX > fromX+1) {
9462         board[fromY][fromX] = EmptySquare;
9463         board[toY][toX] = king;
9464         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9465         board[fromY][BOARD_RGHT-1] = EmptySquare;
9466     } else if (board[fromY][fromX] == king
9467         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9468                && toY == fromY && toX < fromX-1) {
9469         board[fromY][fromX] = EmptySquare;
9470         board[toY][toX] = king;
9471         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9472         board[fromY][BOARD_LEFT] = EmptySquare;
9473     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9474                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9475                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9476                ) {
9477         /* white pawn promotion */
9478         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9479         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9480             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9481         board[fromY][fromX] = EmptySquare;
9482     } else if ((fromY >= BOARD_HEIGHT>>1)
9483                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9484                && (toX != fromX)
9485                && gameInfo.variant != VariantXiangqi
9486                && gameInfo.variant != VariantBerolina
9487                && (board[fromY][fromX] == WhitePawn)
9488                && (board[toY][toX] == EmptySquare)) {
9489         board[fromY][fromX] = EmptySquare;
9490         board[toY][toX] = WhitePawn;
9491         captured = board[toY - 1][toX];
9492         board[toY - 1][toX] = EmptySquare;
9493     } else if ((fromY == BOARD_HEIGHT-4)
9494                && (toX == fromX)
9495                && gameInfo.variant == VariantBerolina
9496                && (board[fromY][fromX] == WhitePawn)
9497                && (board[toY][toX] == EmptySquare)) {
9498         board[fromY][fromX] = EmptySquare;
9499         board[toY][toX] = WhitePawn;
9500         if(oldEP & EP_BEROLIN_A) {
9501                 captured = board[fromY][fromX-1];
9502                 board[fromY][fromX-1] = EmptySquare;
9503         }else{  captured = board[fromY][fromX+1];
9504                 board[fromY][fromX+1] = EmptySquare;
9505         }
9506     } else if (board[fromY][fromX] == king
9507         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9508                && toY == fromY && toX > fromX+1) {
9509         board[fromY][fromX] = EmptySquare;
9510         board[toY][toX] = king;
9511         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9512         board[fromY][BOARD_RGHT-1] = EmptySquare;
9513     } else if (board[fromY][fromX] == king
9514         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9515                && toY == fromY && toX < fromX-1) {
9516         board[fromY][fromX] = EmptySquare;
9517         board[toY][toX] = king;
9518         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9519         board[fromY][BOARD_LEFT] = EmptySquare;
9520     } else if (fromY == 7 && fromX == 3
9521                && board[fromY][fromX] == BlackKing
9522                && toY == 7 && toX == 5) {
9523         board[fromY][fromX] = EmptySquare;
9524         board[toY][toX] = BlackKing;
9525         board[fromY][7] = EmptySquare;
9526         board[toY][4] = BlackRook;
9527     } else if (fromY == 7 && fromX == 3
9528                && board[fromY][fromX] == BlackKing
9529                && toY == 7 && toX == 1) {
9530         board[fromY][fromX] = EmptySquare;
9531         board[toY][toX] = BlackKing;
9532         board[fromY][0] = EmptySquare;
9533         board[toY][2] = BlackRook;
9534     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9535                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9536                && toY < promoRank && promoChar
9537                ) {
9538         /* black pawn promotion */
9539         board[toY][toX] = CharToPiece(ToLower(promoChar));
9540         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9541             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9542         board[fromY][fromX] = EmptySquare;
9543     } else if ((fromY < BOARD_HEIGHT>>1)
9544                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9545                && (toX != fromX)
9546                && gameInfo.variant != VariantXiangqi
9547                && gameInfo.variant != VariantBerolina
9548                && (board[fromY][fromX] == BlackPawn)
9549                && (board[toY][toX] == EmptySquare)) {
9550         board[fromY][fromX] = EmptySquare;
9551         board[toY][toX] = BlackPawn;
9552         captured = board[toY + 1][toX];
9553         board[toY + 1][toX] = EmptySquare;
9554     } else if ((fromY == 3)
9555                && (toX == fromX)
9556                && gameInfo.variant == VariantBerolina
9557                && (board[fromY][fromX] == BlackPawn)
9558                && (board[toY][toX] == EmptySquare)) {
9559         board[fromY][fromX] = EmptySquare;
9560         board[toY][toX] = BlackPawn;
9561         if(oldEP & EP_BEROLIN_A) {
9562                 captured = board[fromY][fromX-1];
9563                 board[fromY][fromX-1] = EmptySquare;
9564         }else{  captured = board[fromY][fromX+1];
9565                 board[fromY][fromX+1] = EmptySquare;
9566         }
9567     } else {
9568         board[toY][toX] = board[fromY][fromX];
9569         board[fromY][fromX] = EmptySquare;
9570     }
9571   }
9572
9573     if (gameInfo.holdingsWidth != 0) {
9574
9575       /* !!A lot more code needs to be written to support holdings  */
9576       /* [HGM] OK, so I have written it. Holdings are stored in the */
9577       /* penultimate board files, so they are automaticlly stored   */
9578       /* in the game history.                                       */
9579       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9580                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9581         /* Delete from holdings, by decreasing count */
9582         /* and erasing image if necessary            */
9583         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9584         if(p < (int) BlackPawn) { /* white drop */
9585              p -= (int)WhitePawn;
9586                  p = PieceToNumber((ChessSquare)p);
9587              if(p >= gameInfo.holdingsSize) p = 0;
9588              if(--board[p][BOARD_WIDTH-2] <= 0)
9589                   board[p][BOARD_WIDTH-1] = EmptySquare;
9590              if((int)board[p][BOARD_WIDTH-2] < 0)
9591                         board[p][BOARD_WIDTH-2] = 0;
9592         } else {                  /* black drop */
9593              p -= (int)BlackPawn;
9594                  p = PieceToNumber((ChessSquare)p);
9595              if(p >= gameInfo.holdingsSize) p = 0;
9596              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9597                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9598              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9599                         board[BOARD_HEIGHT-1-p][1] = 0;
9600         }
9601       }
9602       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9603           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9604         /* [HGM] holdings: Add to holdings, if holdings exist */
9605         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9606                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9607                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9608         }
9609         p = (int) captured;
9610         if (p >= (int) BlackPawn) {
9611           p -= (int)BlackPawn;
9612           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9613                   /* in Shogi restore piece to its original  first */
9614                   captured = (ChessSquare) (DEMOTED captured);
9615                   p = DEMOTED p;
9616           }
9617           p = PieceToNumber((ChessSquare)p);
9618           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9619           board[p][BOARD_WIDTH-2]++;
9620           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9621         } else {
9622           p -= (int)WhitePawn;
9623           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9624                   captured = (ChessSquare) (DEMOTED captured);
9625                   p = DEMOTED p;
9626           }
9627           p = PieceToNumber((ChessSquare)p);
9628           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9629           board[BOARD_HEIGHT-1-p][1]++;
9630           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9631         }
9632       }
9633     } else if (gameInfo.variant == VariantAtomic) {
9634       if (captured != EmptySquare) {
9635         int y, x;
9636         for (y = toY-1; y <= toY+1; y++) {
9637           for (x = toX-1; x <= toX+1; x++) {
9638             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9639                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9640               board[y][x] = EmptySquare;
9641             }
9642           }
9643         }
9644         board[toY][toX] = EmptySquare;
9645       }
9646     }
9647     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9648         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9649     } else
9650     if(promoChar == '+') {
9651         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9652         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9653     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9654         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9655         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9656            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9657         board[toY][toX] = newPiece;
9658     }
9659     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9660                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9661         // [HGM] superchess: take promotion piece out of holdings
9662         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9663         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9664             if(!--board[k][BOARD_WIDTH-2])
9665                 board[k][BOARD_WIDTH-1] = EmptySquare;
9666         } else {
9667             if(!--board[BOARD_HEIGHT-1-k][1])
9668                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9669         }
9670     }
9671
9672 }
9673
9674 /* Updates forwardMostMove */
9675 void
9676 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9677 {
9678 //    forwardMostMove++; // [HGM] bare: moved downstream
9679
9680     (void) CoordsToAlgebraic(boards[forwardMostMove],
9681                              PosFlags(forwardMostMove),
9682                              fromY, fromX, toY, toX, promoChar,
9683                              parseList[forwardMostMove]);
9684
9685     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9686         int timeLeft; static int lastLoadFlag=0; int king, piece;
9687         piece = boards[forwardMostMove][fromY][fromX];
9688         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9689         if(gameInfo.variant == VariantKnightmate)
9690             king += (int) WhiteUnicorn - (int) WhiteKing;
9691         if(forwardMostMove == 0) {
9692             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9693                 fprintf(serverMoves, "%s;", UserName());
9694             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9695                 fprintf(serverMoves, "%s;", second.tidy);
9696             fprintf(serverMoves, "%s;", first.tidy);
9697             if(gameMode == MachinePlaysWhite)
9698                 fprintf(serverMoves, "%s;", UserName());
9699             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9700                 fprintf(serverMoves, "%s;", second.tidy);
9701         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9702         lastLoadFlag = loadFlag;
9703         // print base move
9704         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9705         // print castling suffix
9706         if( toY == fromY && piece == king ) {
9707             if(toX-fromX > 1)
9708                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9709             if(fromX-toX >1)
9710                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9711         }
9712         // e.p. suffix
9713         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9714              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9715              boards[forwardMostMove][toY][toX] == EmptySquare
9716              && fromX != toX && fromY != toY)
9717                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9718         // promotion suffix
9719         if(promoChar != NULLCHAR) {
9720             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9721                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9722                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9723             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9724         }
9725         if(!loadFlag) {
9726                 char buf[MOVE_LEN*2], *p; int len;
9727             fprintf(serverMoves, "/%d/%d",
9728                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9729             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9730             else                      timeLeft = blackTimeRemaining/1000;
9731             fprintf(serverMoves, "/%d", timeLeft);
9732                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9733                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9734                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9735                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9736             fprintf(serverMoves, "/%s", buf);
9737         }
9738         fflush(serverMoves);
9739     }
9740
9741     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9742         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9743       return;
9744     }
9745     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9746     if (commentList[forwardMostMove+1] != NULL) {
9747         free(commentList[forwardMostMove+1]);
9748         commentList[forwardMostMove+1] = NULL;
9749     }
9750     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9751     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9752     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9753     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9754     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9755     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9756     adjustedClock = FALSE;
9757     gameInfo.result = GameUnfinished;
9758     if (gameInfo.resultDetails != NULL) {
9759         free(gameInfo.resultDetails);
9760         gameInfo.resultDetails = NULL;
9761     }
9762     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9763                               moveList[forwardMostMove - 1]);
9764     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9765       case MT_NONE:
9766       case MT_STALEMATE:
9767       default:
9768         break;
9769       case MT_CHECK:
9770         if(gameInfo.variant != VariantShogi)
9771             strcat(parseList[forwardMostMove - 1], "+");
9772         break;
9773       case MT_CHECKMATE:
9774       case MT_STAINMATE:
9775         strcat(parseList[forwardMostMove - 1], "#");
9776         break;
9777     }
9778
9779 }
9780
9781 /* Updates currentMove if not pausing */
9782 void
9783 ShowMove (int fromX, int fromY, int toX, int toY)
9784 {
9785     int instant = (gameMode == PlayFromGameFile) ?
9786         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9787     if(appData.noGUI) return;
9788     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9789         if (!instant) {
9790             if (forwardMostMove == currentMove + 1) {
9791                 AnimateMove(boards[forwardMostMove - 1],
9792                             fromX, fromY, toX, toY);
9793             }
9794         }
9795         currentMove = forwardMostMove;
9796     }
9797
9798     if (instant) return;
9799
9800     DisplayMove(currentMove - 1);
9801     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9802             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9803                 SetHighlights(fromX, fromY, toX, toY);
9804             }
9805     }
9806     DrawPosition(FALSE, boards[currentMove]);
9807     DisplayBothClocks();
9808     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9809 }
9810
9811 void
9812 SendEgtPath (ChessProgramState *cps)
9813 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9814         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9815
9816         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9817
9818         while(*p) {
9819             char c, *q = name+1, *r, *s;
9820
9821             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9822             while(*p && *p != ',') *q++ = *p++;
9823             *q++ = ':'; *q = 0;
9824             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9825                 strcmp(name, ",nalimov:") == 0 ) {
9826                 // take nalimov path from the menu-changeable option first, if it is defined
9827               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9828                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9829             } else
9830             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9831                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9832                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9833                 s = r = StrStr(s, ":") + 1; // beginning of path info
9834                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9835                 c = *r; *r = 0;             // temporarily null-terminate path info
9836                     *--q = 0;               // strip of trailig ':' from name
9837                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9838                 *r = c;
9839                 SendToProgram(buf,cps);     // send egtbpath command for this format
9840             }
9841             if(*p == ',') p++; // read away comma to position for next format name
9842         }
9843 }
9844
9845 void
9846 InitChessProgram (ChessProgramState *cps, int setup)
9847 /* setup needed to setup FRC opening position */
9848 {
9849     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9850     if (appData.noChessProgram) return;
9851     hintRequested = FALSE;
9852     bookRequested = FALSE;
9853
9854     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9855     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9856     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9857     if(cps->memSize) { /* [HGM] memory */
9858       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9859         SendToProgram(buf, cps);
9860     }
9861     SendEgtPath(cps); /* [HGM] EGT */
9862     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9863       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9864         SendToProgram(buf, cps);
9865     }
9866
9867     SendToProgram(cps->initString, cps);
9868     if (gameInfo.variant != VariantNormal &&
9869         gameInfo.variant != VariantLoadable
9870         /* [HGM] also send variant if board size non-standard */
9871         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9872                                             ) {
9873       char *v = VariantName(gameInfo.variant);
9874       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9875         /* [HGM] in protocol 1 we have to assume all variants valid */
9876         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9877         DisplayFatalError(buf, 0, 1);
9878         return;
9879       }
9880
9881       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9882       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9883       if( gameInfo.variant == VariantXiangqi )
9884            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9885       if( gameInfo.variant == VariantShogi )
9886            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9887       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9888            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9889       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9890           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9891            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9892       if( gameInfo.variant == VariantCourier )
9893            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9894       if( gameInfo.variant == VariantSuper )
9895            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9896       if( gameInfo.variant == VariantGreat )
9897            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9898       if( gameInfo.variant == VariantSChess )
9899            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9900       if( gameInfo.variant == VariantGrand )
9901            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9902
9903       if(overruled) {
9904         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9905                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9906            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9907            if(StrStr(cps->variants, b) == NULL) {
9908                // specific sized variant not known, check if general sizing allowed
9909                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9910                    if(StrStr(cps->variants, "boardsize") == NULL) {
9911                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9912                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9913                        DisplayFatalError(buf, 0, 1);
9914                        return;
9915                    }
9916                    /* [HGM] here we really should compare with the maximum supported board size */
9917                }
9918            }
9919       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9920       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9921       SendToProgram(buf, cps);
9922     }
9923     currentlyInitializedVariant = gameInfo.variant;
9924
9925     /* [HGM] send opening position in FRC to first engine */
9926     if(setup) {
9927           SendToProgram("force\n", cps);
9928           SendBoard(cps, 0);
9929           /* engine is now in force mode! Set flag to wake it up after first move. */
9930           setboardSpoiledMachineBlack = 1;
9931     }
9932
9933     if (cps->sendICS) {
9934       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9935       SendToProgram(buf, cps);
9936     }
9937     cps->maybeThinking = FALSE;
9938     cps->offeredDraw = 0;
9939     if (!appData.icsActive) {
9940         SendTimeControl(cps, movesPerSession, timeControl,
9941                         timeIncrement, appData.searchDepth,
9942                         searchTime);
9943     }
9944     if (appData.showThinking
9945         // [HGM] thinking: four options require thinking output to be sent
9946         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9947                                 ) {
9948         SendToProgram("post\n", cps);
9949     }
9950     SendToProgram("hard\n", cps);
9951     if (!appData.ponderNextMove) {
9952         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9953            it without being sure what state we are in first.  "hard"
9954            is not a toggle, so that one is OK.
9955          */
9956         SendToProgram("easy\n", cps);
9957     }
9958     if (cps->usePing) {
9959       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9960       SendToProgram(buf, cps);
9961     }
9962     cps->initDone = TRUE;
9963     ClearEngineOutputPane(cps == &second);
9964 }
9965
9966
9967 void
9968 ResendOptions (ChessProgramState *cps)
9969 { // send the stored value of the options
9970   int i;
9971   char buf[MSG_SIZ];
9972   Option *opt = cps->option;
9973   for(i=0; i<cps->nrOptions; i++, opt++) {
9974       switch(opt->type) {
9975         case Spin:
9976         case Slider:
9977         case CheckBox:
9978             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9979           break;
9980         case ComboBox:
9981           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9982           break;
9983         default:
9984             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9985           break;
9986         case Button:
9987         case SaveButton:
9988           continue;
9989       }
9990       SendToProgram(buf, cps);
9991   }
9992 }
9993
9994 void
9995 StartChessProgram (ChessProgramState *cps)
9996 {
9997     char buf[MSG_SIZ];
9998     int err;
9999
10000     if (appData.noChessProgram) return;
10001     cps->initDone = FALSE;
10002
10003     if (strcmp(cps->host, "localhost") == 0) {
10004         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10005     } else if (*appData.remoteShell == NULLCHAR) {
10006         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10007     } else {
10008         if (*appData.remoteUser == NULLCHAR) {
10009           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10010                     cps->program);
10011         } else {
10012           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10013                     cps->host, appData.remoteUser, cps->program);
10014         }
10015         err = StartChildProcess(buf, "", &cps->pr);
10016     }
10017
10018     if (err != 0) {
10019       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10020         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10021         if(cps != &first) return;
10022         appData.noChessProgram = TRUE;
10023         ThawUI();
10024         SetNCPMode();
10025 //      DisplayFatalError(buf, err, 1);
10026 //      cps->pr = NoProc;
10027 //      cps->isr = NULL;
10028         return;
10029     }
10030
10031     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10032     if (cps->protocolVersion > 1) {
10033       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10034       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10035         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10036         cps->comboCnt = 0;  //                and values of combo boxes
10037       }
10038       SendToProgram(buf, cps);
10039       if(cps->reload) ResendOptions(cps);
10040     } else {
10041       SendToProgram("xboard\n", cps);
10042     }
10043 }
10044
10045 void
10046 TwoMachinesEventIfReady P((void))
10047 {
10048   static int curMess = 0;
10049   if (first.lastPing != first.lastPong) {
10050     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10051     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10052     return;
10053   }
10054   if (second.lastPing != second.lastPong) {
10055     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10056     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10057     return;
10058   }
10059   DisplayMessage("", ""); curMess = 0;
10060   TwoMachinesEvent();
10061 }
10062
10063 char *
10064 MakeName (char *template)
10065 {
10066     time_t clock;
10067     struct tm *tm;
10068     static char buf[MSG_SIZ];
10069     char *p = buf;
10070     int i;
10071
10072     clock = time((time_t *)NULL);
10073     tm = localtime(&clock);
10074
10075     while(*p++ = *template++) if(p[-1] == '%') {
10076         switch(*template++) {
10077           case 0:   *p = 0; return buf;
10078           case 'Y': i = tm->tm_year+1900; break;
10079           case 'y': i = tm->tm_year-100; break;
10080           case 'M': i = tm->tm_mon+1; break;
10081           case 'd': i = tm->tm_mday; break;
10082           case 'h': i = tm->tm_hour; break;
10083           case 'm': i = tm->tm_min; break;
10084           case 's': i = tm->tm_sec; break;
10085           default:  i = 0;
10086         }
10087         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10088     }
10089     return buf;
10090 }
10091
10092 int
10093 CountPlayers (char *p)
10094 {
10095     int n = 0;
10096     while(p = strchr(p, '\n')) p++, n++; // count participants
10097     return n;
10098 }
10099
10100 FILE *
10101 WriteTourneyFile (char *results, FILE *f)
10102 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10103     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10104     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10105         // create a file with tournament description
10106         fprintf(f, "-participants {%s}\n", appData.participants);
10107         fprintf(f, "-seedBase %d\n", appData.seedBase);
10108         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10109         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10110         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10111         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10112         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10113         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10114         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10115         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10116         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10117         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10118         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10119         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10120         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10121         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10122         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10123         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10124         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10125         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10126         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10127         fprintf(f, "-smpCores %d\n", appData.smpCores);
10128         if(searchTime > 0)
10129                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10130         else {
10131                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10132                 fprintf(f, "-tc %s\n", appData.timeControl);
10133                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10134         }
10135         fprintf(f, "-results \"%s\"\n", results);
10136     }
10137     return f;
10138 }
10139
10140 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10141
10142 void
10143 Substitute (char *participants, int expunge)
10144 {
10145     int i, changed, changes=0, nPlayers=0;
10146     char *p, *q, *r, buf[MSG_SIZ];
10147     if(participants == NULL) return;
10148     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10149     r = p = participants; q = appData.participants;
10150     while(*p && *p == *q) {
10151         if(*p == '\n') r = p+1, nPlayers++;
10152         p++; q++;
10153     }
10154     if(*p) { // difference
10155         while(*p && *p++ != '\n');
10156         while(*q && *q++ != '\n');
10157       changed = nPlayers;
10158         changes = 1 + (strcmp(p, q) != 0);
10159     }
10160     if(changes == 1) { // a single engine mnemonic was changed
10161         q = r; while(*q) nPlayers += (*q++ == '\n');
10162         p = buf; while(*r && (*p = *r++) != '\n') p++;
10163         *p = NULLCHAR;
10164         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10165         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10166         if(mnemonic[i]) { // The substitute is valid
10167             FILE *f;
10168             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10169                 flock(fileno(f), LOCK_EX);
10170                 ParseArgsFromFile(f);
10171                 fseek(f, 0, SEEK_SET);
10172                 FREE(appData.participants); appData.participants = participants;
10173                 if(expunge) { // erase results of replaced engine
10174                     int len = strlen(appData.results), w, b, dummy;
10175                     for(i=0; i<len; i++) {
10176                         Pairing(i, nPlayers, &w, &b, &dummy);
10177                         if((w == changed || b == changed) && appData.results[i] == '*') {
10178                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10179                             fclose(f);
10180                             return;
10181                         }
10182                     }
10183                     for(i=0; i<len; i++) {
10184                         Pairing(i, nPlayers, &w, &b, &dummy);
10185                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10186                     }
10187                 }
10188                 WriteTourneyFile(appData.results, f);
10189                 fclose(f); // release lock
10190                 return;
10191             }
10192         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10193     }
10194     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10195     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10196     free(participants);
10197     return;
10198 }
10199
10200 int
10201 CheckPlayers (char *participants)
10202 {
10203         int i;
10204         char buf[MSG_SIZ], *p;
10205         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10206         while(p = strchr(participants, '\n')) {
10207             *p = NULLCHAR;
10208             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10209             if(!mnemonic[i]) {
10210                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10211                 *p = '\n';
10212                 DisplayError(buf, 0);
10213                 return 1;
10214             }
10215             *p = '\n';
10216             participants = p + 1;
10217         }
10218         return 0;
10219 }
10220
10221 int
10222 CreateTourney (char *name)
10223 {
10224         FILE *f;
10225         if(matchMode && strcmp(name, appData.tourneyFile)) {
10226              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10227         }
10228         if(name[0] == NULLCHAR) {
10229             if(appData.participants[0])
10230                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10231             return 0;
10232         }
10233         f = fopen(name, "r");
10234         if(f) { // file exists
10235             ASSIGN(appData.tourneyFile, name);
10236             ParseArgsFromFile(f); // parse it
10237         } else {
10238             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10239             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10240                 DisplayError(_("Not enough participants"), 0);
10241                 return 0;
10242             }
10243             if(CheckPlayers(appData.participants)) return 0;
10244             ASSIGN(appData.tourneyFile, name);
10245             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10246             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10247         }
10248         fclose(f);
10249         appData.noChessProgram = FALSE;
10250         appData.clockMode = TRUE;
10251         SetGNUMode();
10252         return 1;
10253 }
10254
10255 int
10256 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10257 {
10258     char buf[MSG_SIZ], *p, *q;
10259     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10260     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10261     skip = !all && group[0]; // if group requested, we start in skip mode
10262     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10263         p = names; q = buf; header = 0;
10264         while(*p && *p != '\n') *q++ = *p++;
10265         *q = 0;
10266         if(*p == '\n') p++;
10267         if(buf[0] == '#') {
10268             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10269             depth++; // we must be entering a new group
10270             if(all) continue; // suppress printing group headers when complete list requested
10271             header = 1;
10272             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10273         }
10274         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10275         if(engineList[i]) free(engineList[i]);
10276         engineList[i] = strdup(buf);
10277         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10278         if(engineMnemonic[i]) free(engineMnemonic[i]);
10279         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10280             strcat(buf, " (");
10281             sscanf(q + 8, "%s", buf + strlen(buf));
10282             strcat(buf, ")");
10283         }
10284         engineMnemonic[i] = strdup(buf);
10285         i++;
10286     }
10287     engineList[i] = engineMnemonic[i] = NULL;
10288     return i;
10289 }
10290
10291 // following implemented as macro to avoid type limitations
10292 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10293
10294 void
10295 SwapEngines (int n)
10296 {   // swap settings for first engine and other engine (so far only some selected options)
10297     int h;
10298     char *p;
10299     if(n == 0) return;
10300     SWAP(directory, p)
10301     SWAP(chessProgram, p)
10302     SWAP(isUCI, h)
10303     SWAP(hasOwnBookUCI, h)
10304     SWAP(protocolVersion, h)
10305     SWAP(reuse, h)
10306     SWAP(scoreIsAbsolute, h)
10307     SWAP(timeOdds, h)
10308     SWAP(logo, p)
10309     SWAP(pgnName, p)
10310     SWAP(pvSAN, h)
10311     SWAP(engOptions, p)
10312     SWAP(engInitString, p)
10313     SWAP(computerString, p)
10314     SWAP(features, p)
10315     SWAP(fenOverride, p)
10316     SWAP(NPS, h)
10317     SWAP(accumulateTC, h)
10318     SWAP(host, p)
10319 }
10320
10321 int
10322 GetEngineLine (char *s, int n)
10323 {
10324     int i;
10325     char buf[MSG_SIZ];
10326     extern char *icsNames;
10327     if(!s || !*s) return 0;
10328     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10329     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10330     if(!mnemonic[i]) return 0;
10331     if(n == 11) return 1; // just testing if there was a match
10332     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10333     if(n == 1) SwapEngines(n);
10334     ParseArgsFromString(buf);
10335     if(n == 1) SwapEngines(n);
10336     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10337         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10338         ParseArgsFromString(buf);
10339     }
10340     return 1;
10341 }
10342
10343 int
10344 SetPlayer (int player, char *p)
10345 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10346     int i;
10347     char buf[MSG_SIZ], *engineName;
10348     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10349     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10350     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10351     if(mnemonic[i]) {
10352         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10353         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10354         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10355         ParseArgsFromString(buf);
10356     } else { // no engine with this nickname is installed!
10357         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10358         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10359         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10360         ModeHighlight();
10361         DisplayError(buf, 0);
10362         return 0;
10363     }
10364     free(engineName);
10365     return i;
10366 }
10367
10368 char *recentEngines;
10369
10370 void
10371 RecentEngineEvent (int nr)
10372 {
10373     int n;
10374 //    SwapEngines(1); // bump first to second
10375 //    ReplaceEngine(&second, 1); // and load it there
10376     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10377     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10378     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10379         ReplaceEngine(&first, 0);
10380         FloatToFront(&appData.recentEngineList, command[n]);
10381     }
10382 }
10383
10384 int
10385 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10386 {   // determine players from game number
10387     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10388
10389     if(appData.tourneyType == 0) {
10390         roundsPerCycle = (nPlayers - 1) | 1;
10391         pairingsPerRound = nPlayers / 2;
10392     } else if(appData.tourneyType > 0) {
10393         roundsPerCycle = nPlayers - appData.tourneyType;
10394         pairingsPerRound = appData.tourneyType;
10395     }
10396     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10397     gamesPerCycle = gamesPerRound * roundsPerCycle;
10398     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10399     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10400     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10401     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10402     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10403     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10404
10405     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10406     if(appData.roundSync) *syncInterval = gamesPerRound;
10407
10408     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10409
10410     if(appData.tourneyType == 0) {
10411         if(curPairing == (nPlayers-1)/2 ) {
10412             *whitePlayer = curRound;
10413             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10414         } else {
10415             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10416             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10417             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10418             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10419         }
10420     } else if(appData.tourneyType > 1) {
10421         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10422         *whitePlayer = curRound + appData.tourneyType;
10423     } else if(appData.tourneyType > 0) {
10424         *whitePlayer = curPairing;
10425         *blackPlayer = curRound + appData.tourneyType;
10426     }
10427
10428     // take care of white/black alternation per round.
10429     // For cycles and games this is already taken care of by default, derived from matchGame!
10430     return curRound & 1;
10431 }
10432
10433 int
10434 NextTourneyGame (int nr, int *swapColors)
10435 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10436     char *p, *q;
10437     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10438     FILE *tf;
10439     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10440     tf = fopen(appData.tourneyFile, "r");
10441     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10442     ParseArgsFromFile(tf); fclose(tf);
10443     InitTimeControls(); // TC might be altered from tourney file
10444
10445     nPlayers = CountPlayers(appData.participants); // count participants
10446     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10447     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10448
10449     if(syncInterval) {
10450         p = q = appData.results;
10451         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10452         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10453             DisplayMessage(_("Waiting for other game(s)"),"");
10454             waitingForGame = TRUE;
10455             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10456             return 0;
10457         }
10458         waitingForGame = FALSE;
10459     }
10460
10461     if(appData.tourneyType < 0) {
10462         if(nr>=0 && !pairingReceived) {
10463             char buf[1<<16];
10464             if(pairing.pr == NoProc) {
10465                 if(!appData.pairingEngine[0]) {
10466                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10467                     return 0;
10468                 }
10469                 StartChessProgram(&pairing); // starts the pairing engine
10470             }
10471             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10472             SendToProgram(buf, &pairing);
10473             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10474             SendToProgram(buf, &pairing);
10475             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10476         }
10477         pairingReceived = 0;                              // ... so we continue here
10478         *swapColors = 0;
10479         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10480         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10481         matchGame = 1; roundNr = nr / syncInterval + 1;
10482     }
10483
10484     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10485
10486     // redefine engines, engine dir, etc.
10487     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10488     if(first.pr == NoProc) {
10489       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10490       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10491     }
10492     if(second.pr == NoProc) {
10493       SwapEngines(1);
10494       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10495       SwapEngines(1);         // and make that valid for second engine by swapping
10496       InitEngine(&second, 1);
10497     }
10498     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10499     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10500     return OK;
10501 }
10502
10503 void
10504 NextMatchGame ()
10505 {   // performs game initialization that does not invoke engines, and then tries to start the game
10506     int res, firstWhite, swapColors = 0;
10507     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10508     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
10509         char buf[MSG_SIZ];
10510         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10511         if(strcmp(buf, currentDebugFile)) { // name has changed
10512             FILE *f = fopen(buf, "w");
10513             if(f) { // if opening the new file failed, just keep using the old one
10514                 ASSIGN(currentDebugFile, buf);
10515                 fclose(debugFP);
10516                 debugFP = f;
10517             }
10518             if(appData.serverFileName) {
10519                 if(serverFP) fclose(serverFP);
10520                 serverFP = fopen(appData.serverFileName, "w");
10521                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10522                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10523             }
10524         }
10525     }
10526     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10527     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10528     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10529     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10530     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10531     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10532     Reset(FALSE, first.pr != NoProc);
10533     res = LoadGameOrPosition(matchGame); // setup game
10534     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10535     if(!res) return; // abort when bad game/pos file
10536     TwoMachinesEvent();
10537 }
10538
10539 void
10540 UserAdjudicationEvent (int result)
10541 {
10542     ChessMove gameResult = GameIsDrawn;
10543
10544     if( result > 0 ) {
10545         gameResult = WhiteWins;
10546     }
10547     else if( result < 0 ) {
10548         gameResult = BlackWins;
10549     }
10550
10551     if( gameMode == TwoMachinesPlay ) {
10552         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10553     }
10554 }
10555
10556
10557 // [HGM] save: calculate checksum of game to make games easily identifiable
10558 int
10559 StringCheckSum (char *s)
10560 {
10561         int i = 0;
10562         if(s==NULL) return 0;
10563         while(*s) i = i*259 + *s++;
10564         return i;
10565 }
10566
10567 int
10568 GameCheckSum ()
10569 {
10570         int i, sum=0;
10571         for(i=backwardMostMove; i<forwardMostMove; i++) {
10572                 sum += pvInfoList[i].depth;
10573                 sum += StringCheckSum(parseList[i]);
10574                 sum += StringCheckSum(commentList[i]);
10575                 sum *= 261;
10576         }
10577         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10578         return sum + StringCheckSum(commentList[i]);
10579 } // end of save patch
10580
10581 void
10582 GameEnds (ChessMove result, char *resultDetails, int whosays)
10583 {
10584     GameMode nextGameMode;
10585     int isIcsGame;
10586     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10587
10588     if(endingGame) return; /* [HGM] crash: forbid recursion */
10589     endingGame = 1;
10590     if(twoBoards) { // [HGM] dual: switch back to one board
10591         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10592         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10593     }
10594     if (appData.debugMode) {
10595       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10596               result, resultDetails ? resultDetails : "(null)", whosays);
10597     }
10598
10599     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10600
10601     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10602
10603     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10604         /* If we are playing on ICS, the server decides when the
10605            game is over, but the engine can offer to draw, claim
10606            a draw, or resign.
10607          */
10608 #if ZIPPY
10609         if (appData.zippyPlay && first.initDone) {
10610             if (result == GameIsDrawn) {
10611                 /* In case draw still needs to be claimed */
10612                 SendToICS(ics_prefix);
10613                 SendToICS("draw\n");
10614             } else if (StrCaseStr(resultDetails, "resign")) {
10615                 SendToICS(ics_prefix);
10616                 SendToICS("resign\n");
10617             }
10618         }
10619 #endif
10620         endingGame = 0; /* [HGM] crash */
10621         return;
10622     }
10623
10624     /* If we're loading the game from a file, stop */
10625     if (whosays == GE_FILE) {
10626       (void) StopLoadGameTimer();
10627       gameFileFP = NULL;
10628     }
10629
10630     /* Cancel draw offers */
10631     first.offeredDraw = second.offeredDraw = 0;
10632
10633     /* If this is an ICS game, only ICS can really say it's done;
10634        if not, anyone can. */
10635     isIcsGame = (gameMode == IcsPlayingWhite ||
10636                  gameMode == IcsPlayingBlack ||
10637                  gameMode == IcsObserving    ||
10638                  gameMode == IcsExamining);
10639
10640     if (!isIcsGame || whosays == GE_ICS) {
10641         /* OK -- not an ICS game, or ICS said it was done */
10642         StopClocks();
10643         if (!isIcsGame && !appData.noChessProgram)
10644           SetUserThinkingEnables();
10645
10646         /* [HGM] if a machine claims the game end we verify this claim */
10647         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10648             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10649                 char claimer;
10650                 ChessMove trueResult = (ChessMove) -1;
10651
10652                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10653                                             first.twoMachinesColor[0] :
10654                                             second.twoMachinesColor[0] ;
10655
10656                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10657                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10658                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10659                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10660                 } else
10661                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10662                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10663                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10664                 } else
10665                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10666                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10667                 }
10668
10669                 // now verify win claims, but not in drop games, as we don't understand those yet
10670                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10671                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10672                     (result == WhiteWins && claimer == 'w' ||
10673                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10674                       if (appData.debugMode) {
10675                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10676                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10677                       }
10678                       if(result != trueResult) {
10679                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10680                               result = claimer == 'w' ? BlackWins : WhiteWins;
10681                               resultDetails = buf;
10682                       }
10683                 } else
10684                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10685                     && (forwardMostMove <= backwardMostMove ||
10686                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10687                         (claimer=='b')==(forwardMostMove&1))
10688                                                                                   ) {
10689                       /* [HGM] verify: draws that were not flagged are false claims */
10690                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10691                       result = claimer == 'w' ? BlackWins : WhiteWins;
10692                       resultDetails = buf;
10693                 }
10694                 /* (Claiming a loss is accepted no questions asked!) */
10695             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10696                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10697                 result = GameUnfinished;
10698                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10699             }
10700             /* [HGM] bare: don't allow bare King to win */
10701             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10702                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10703                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10704                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10705                && result != GameIsDrawn)
10706             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10707                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10708                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10709                         if(p >= 0 && p <= (int)WhiteKing) k++;
10710                 }
10711                 if (appData.debugMode) {
10712                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10713                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10714                 }
10715                 if(k <= 1) {
10716                         result = GameIsDrawn;
10717                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10718                         resultDetails = buf;
10719                 }
10720             }
10721         }
10722
10723
10724         if(serverMoves != NULL && !loadFlag) { char c = '=';
10725             if(result==WhiteWins) c = '+';
10726             if(result==BlackWins) c = '-';
10727             if(resultDetails != NULL)
10728                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10729         }
10730         if (resultDetails != NULL) {
10731             gameInfo.result = result;
10732             gameInfo.resultDetails = StrSave(resultDetails);
10733
10734             /* display last move only if game was not loaded from file */
10735             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10736                 DisplayMove(currentMove - 1);
10737
10738             if (forwardMostMove != 0) {
10739                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10740                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10741                                                                 ) {
10742                     if (*appData.saveGameFile != NULLCHAR) {
10743                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10744                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10745                         else
10746                         SaveGameToFile(appData.saveGameFile, TRUE);
10747                     } else if (appData.autoSaveGames) {
10748                         AutoSaveGame();
10749                     }
10750                     if (*appData.savePositionFile != NULLCHAR) {
10751                         SavePositionToFile(appData.savePositionFile);
10752                     }
10753                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10754                 }
10755             }
10756
10757             /* Tell program how game ended in case it is learning */
10758             /* [HGM] Moved this to after saving the PGN, just in case */
10759             /* engine died and we got here through time loss. In that */
10760             /* case we will get a fatal error writing the pipe, which */
10761             /* would otherwise lose us the PGN.                       */
10762             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10763             /* output during GameEnds should never be fatal anymore   */
10764             if (gameMode == MachinePlaysWhite ||
10765                 gameMode == MachinePlaysBlack ||
10766                 gameMode == TwoMachinesPlay ||
10767                 gameMode == IcsPlayingWhite ||
10768                 gameMode == IcsPlayingBlack ||
10769                 gameMode == BeginningOfGame) {
10770                 char buf[MSG_SIZ];
10771                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10772                         resultDetails);
10773                 if (first.pr != NoProc) {
10774                     SendToProgram(buf, &first);
10775                 }
10776                 if (second.pr != NoProc &&
10777                     gameMode == TwoMachinesPlay) {
10778                     SendToProgram(buf, &second);
10779                 }
10780             }
10781         }
10782
10783         if (appData.icsActive) {
10784             if (appData.quietPlay &&
10785                 (gameMode == IcsPlayingWhite ||
10786                  gameMode == IcsPlayingBlack)) {
10787                 SendToICS(ics_prefix);
10788                 SendToICS("set shout 1\n");
10789             }
10790             nextGameMode = IcsIdle;
10791             ics_user_moved = FALSE;
10792             /* clean up premove.  It's ugly when the game has ended and the
10793              * premove highlights are still on the board.
10794              */
10795             if (gotPremove) {
10796               gotPremove = FALSE;
10797               ClearPremoveHighlights();
10798               DrawPosition(FALSE, boards[currentMove]);
10799             }
10800             if (whosays == GE_ICS) {
10801                 switch (result) {
10802                 case WhiteWins:
10803                     if (gameMode == IcsPlayingWhite)
10804                         PlayIcsWinSound();
10805                     else if(gameMode == IcsPlayingBlack)
10806                         PlayIcsLossSound();
10807                     break;
10808                 case BlackWins:
10809                     if (gameMode == IcsPlayingBlack)
10810                         PlayIcsWinSound();
10811                     else if(gameMode == IcsPlayingWhite)
10812                         PlayIcsLossSound();
10813                     break;
10814                 case GameIsDrawn:
10815                     PlayIcsDrawSound();
10816                     break;
10817                 default:
10818                     PlayIcsUnfinishedSound();
10819                 }
10820             }
10821         } else if (gameMode == EditGame ||
10822                    gameMode == PlayFromGameFile ||
10823                    gameMode == AnalyzeMode ||
10824                    gameMode == AnalyzeFile) {
10825             nextGameMode = gameMode;
10826         } else {
10827             nextGameMode = EndOfGame;
10828         }
10829         pausing = FALSE;
10830         ModeHighlight();
10831     } else {
10832         nextGameMode = gameMode;
10833     }
10834
10835     if (appData.noChessProgram) {
10836         gameMode = nextGameMode;
10837         ModeHighlight();
10838         endingGame = 0; /* [HGM] crash */
10839         return;
10840     }
10841
10842     if (first.reuse) {
10843         /* Put first chess program into idle state */
10844         if (first.pr != NoProc &&
10845             (gameMode == MachinePlaysWhite ||
10846              gameMode == MachinePlaysBlack ||
10847              gameMode == TwoMachinesPlay ||
10848              gameMode == IcsPlayingWhite ||
10849              gameMode == IcsPlayingBlack ||
10850              gameMode == BeginningOfGame)) {
10851             SendToProgram("force\n", &first);
10852             if (first.usePing) {
10853               char buf[MSG_SIZ];
10854               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10855               SendToProgram(buf, &first);
10856             }
10857         }
10858     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10859         /* Kill off first chess program */
10860         if (first.isr != NULL)
10861           RemoveInputSource(first.isr);
10862         first.isr = NULL;
10863
10864         if (first.pr != NoProc) {
10865             ExitAnalyzeMode();
10866             DoSleep( appData.delayBeforeQuit );
10867             SendToProgram("quit\n", &first);
10868             DoSleep( appData.delayAfterQuit );
10869             DestroyChildProcess(first.pr, first.useSigterm);
10870             first.reload = TRUE;
10871         }
10872         first.pr = NoProc;
10873     }
10874     if (second.reuse) {
10875         /* Put second chess program into idle state */
10876         if (second.pr != NoProc &&
10877             gameMode == TwoMachinesPlay) {
10878             SendToProgram("force\n", &second);
10879             if (second.usePing) {
10880               char buf[MSG_SIZ];
10881               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10882               SendToProgram(buf, &second);
10883             }
10884         }
10885     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10886         /* Kill off second chess program */
10887         if (second.isr != NULL)
10888           RemoveInputSource(second.isr);
10889         second.isr = NULL;
10890
10891         if (second.pr != NoProc) {
10892             DoSleep( appData.delayBeforeQuit );
10893             SendToProgram("quit\n", &second);
10894             DoSleep( appData.delayAfterQuit );
10895             DestroyChildProcess(second.pr, second.useSigterm);
10896             second.reload = TRUE;
10897         }
10898         second.pr = NoProc;
10899     }
10900
10901     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10902         char resChar = '=';
10903         switch (result) {
10904         case WhiteWins:
10905           resChar = '+';
10906           if (first.twoMachinesColor[0] == 'w') {
10907             first.matchWins++;
10908           } else {
10909             second.matchWins++;
10910           }
10911           break;
10912         case BlackWins:
10913           resChar = '-';
10914           if (first.twoMachinesColor[0] == 'b') {
10915             first.matchWins++;
10916           } else {
10917             second.matchWins++;
10918           }
10919           break;
10920         case GameUnfinished:
10921           resChar = ' ';
10922         default:
10923           break;
10924         }
10925
10926         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10927         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10928             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10929             ReserveGame(nextGame, resChar); // sets nextGame
10930             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10931             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10932         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10933
10934         if (nextGame <= appData.matchGames && !abortMatch) {
10935             gameMode = nextGameMode;
10936             matchGame = nextGame; // this will be overruled in tourney mode!
10937             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10938             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10939             endingGame = 0; /* [HGM] crash */
10940             return;
10941         } else {
10942             gameMode = nextGameMode;
10943             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10944                      first.tidy, second.tidy,
10945                      first.matchWins, second.matchWins,
10946                      appData.matchGames - (first.matchWins + second.matchWins));
10947             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10948             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10949             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10950             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10951                 first.twoMachinesColor = "black\n";
10952                 second.twoMachinesColor = "white\n";
10953             } else {
10954                 first.twoMachinesColor = "white\n";
10955                 second.twoMachinesColor = "black\n";
10956             }
10957         }
10958     }
10959     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10960         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10961       ExitAnalyzeMode();
10962     gameMode = nextGameMode;
10963     ModeHighlight();
10964     endingGame = 0;  /* [HGM] crash */
10965     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10966         if(matchMode == TRUE) { // match through command line: exit with or without popup
10967             if(ranking) {
10968                 ToNrEvent(forwardMostMove);
10969                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10970                 else ExitEvent(0);
10971             } else DisplayFatalError(buf, 0, 0);
10972         } else { // match through menu; just stop, with or without popup
10973             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10974             ModeHighlight();
10975             if(ranking){
10976                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10977             } else DisplayNote(buf);
10978       }
10979       if(ranking) free(ranking);
10980     }
10981 }
10982
10983 /* Assumes program was just initialized (initString sent).
10984    Leaves program in force mode. */
10985 void
10986 FeedMovesToProgram (ChessProgramState *cps, int upto)
10987 {
10988     int i;
10989
10990     if (appData.debugMode)
10991       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10992               startedFromSetupPosition ? "position and " : "",
10993               backwardMostMove, upto, cps->which);
10994     if(currentlyInitializedVariant != gameInfo.variant) {
10995       char buf[MSG_SIZ];
10996         // [HGM] variantswitch: make engine aware of new variant
10997         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10998                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10999         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11000         SendToProgram(buf, cps);
11001         currentlyInitializedVariant = gameInfo.variant;
11002     }
11003     SendToProgram("force\n", cps);
11004     if (startedFromSetupPosition) {
11005         SendBoard(cps, backwardMostMove);
11006     if (appData.debugMode) {
11007         fprintf(debugFP, "feedMoves\n");
11008     }
11009     }
11010     for (i = backwardMostMove; i < upto; i++) {
11011         SendMoveToProgram(i, cps);
11012     }
11013 }
11014
11015
11016 int
11017 ResurrectChessProgram ()
11018 {
11019      /* The chess program may have exited.
11020         If so, restart it and feed it all the moves made so far. */
11021     static int doInit = 0;
11022
11023     if (appData.noChessProgram) return 1;
11024
11025     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11026         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11027         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11028         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11029     } else {
11030         if (first.pr != NoProc) return 1;
11031         StartChessProgram(&first);
11032     }
11033     InitChessProgram(&first, FALSE);
11034     FeedMovesToProgram(&first, currentMove);
11035
11036     if (!first.sendTime) {
11037         /* can't tell gnuchess what its clock should read,
11038            so we bow to its notion. */
11039         ResetClocks();
11040         timeRemaining[0][currentMove] = whiteTimeRemaining;
11041         timeRemaining[1][currentMove] = blackTimeRemaining;
11042     }
11043
11044     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11045                 appData.icsEngineAnalyze) && first.analysisSupport) {
11046       SendToProgram("analyze\n", &first);
11047       first.analyzing = TRUE;
11048     }
11049     return 1;
11050 }
11051
11052 /*
11053  * Button procedures
11054  */
11055 void
11056 Reset (int redraw, int init)
11057 {
11058     int i;
11059
11060     if (appData.debugMode) {
11061         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11062                 redraw, init, gameMode);
11063     }
11064     CleanupTail(); // [HGM] vari: delete any stored variations
11065     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11066     pausing = pauseExamInvalid = FALSE;
11067     startedFromSetupPosition = blackPlaysFirst = FALSE;
11068     firstMove = TRUE;
11069     whiteFlag = blackFlag = FALSE;
11070     userOfferedDraw = FALSE;
11071     hintRequested = bookRequested = FALSE;
11072     first.maybeThinking = FALSE;
11073     second.maybeThinking = FALSE;
11074     first.bookSuspend = FALSE; // [HGM] book
11075     second.bookSuspend = FALSE;
11076     thinkOutput[0] = NULLCHAR;
11077     lastHint[0] = NULLCHAR;
11078     ClearGameInfo(&gameInfo);
11079     gameInfo.variant = StringToVariant(appData.variant);
11080     ics_user_moved = ics_clock_paused = FALSE;
11081     ics_getting_history = H_FALSE;
11082     ics_gamenum = -1;
11083     white_holding[0] = black_holding[0] = NULLCHAR;
11084     ClearProgramStats();
11085     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11086
11087     ResetFrontEnd();
11088     ClearHighlights();
11089     flipView = appData.flipView;
11090     ClearPremoveHighlights();
11091     gotPremove = FALSE;
11092     alarmSounded = FALSE;
11093
11094     GameEnds(EndOfFile, NULL, GE_PLAYER);
11095     if(appData.serverMovesName != NULL) {
11096         /* [HGM] prepare to make moves file for broadcasting */
11097         clock_t t = clock();
11098         if(serverMoves != NULL) fclose(serverMoves);
11099         serverMoves = fopen(appData.serverMovesName, "r");
11100         if(serverMoves != NULL) {
11101             fclose(serverMoves);
11102             /* delay 15 sec before overwriting, so all clients can see end */
11103             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11104         }
11105         serverMoves = fopen(appData.serverMovesName, "w");
11106     }
11107
11108     ExitAnalyzeMode();
11109     gameMode = BeginningOfGame;
11110     ModeHighlight();
11111     if(appData.icsActive) gameInfo.variant = VariantNormal;
11112     currentMove = forwardMostMove = backwardMostMove = 0;
11113     MarkTargetSquares(1);
11114     InitPosition(redraw);
11115     for (i = 0; i < MAX_MOVES; i++) {
11116         if (commentList[i] != NULL) {
11117             free(commentList[i]);
11118             commentList[i] = NULL;
11119         }
11120     }
11121     ResetClocks();
11122     timeRemaining[0][0] = whiteTimeRemaining;
11123     timeRemaining[1][0] = blackTimeRemaining;
11124
11125     if (first.pr == NoProc) {
11126         StartChessProgram(&first);
11127     }
11128     if (init) {
11129             InitChessProgram(&first, startedFromSetupPosition);
11130     }
11131     DisplayTitle("");
11132     DisplayMessage("", "");
11133     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11134     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11135     ClearMap();        // [HGM] exclude: invalidate map
11136 }
11137
11138 void
11139 AutoPlayGameLoop ()
11140 {
11141     for (;;) {
11142         if (!AutoPlayOneMove())
11143           return;
11144         if (matchMode || appData.timeDelay == 0)
11145           continue;
11146         if (appData.timeDelay < 0)
11147           return;
11148         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11149         break;
11150     }
11151 }
11152
11153 void
11154 AnalyzeNextGame()
11155 {
11156     ReloadGame(1); // next game
11157 }
11158
11159 int
11160 AutoPlayOneMove ()
11161 {
11162     int fromX, fromY, toX, toY;
11163
11164     if (appData.debugMode) {
11165       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11166     }
11167
11168     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11169       return FALSE;
11170
11171     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11172       pvInfoList[currentMove].depth = programStats.depth;
11173       pvInfoList[currentMove].score = programStats.score;
11174       pvInfoList[currentMove].time  = 0;
11175       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11176     }
11177
11178     if (currentMove >= forwardMostMove) {
11179       if(gameMode == AnalyzeFile) {
11180           if(appData.loadGameIndex == -1) {
11181             GameEnds(EndOfFile, NULL, GE_FILE);
11182           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11183           } else {
11184           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11185         }
11186       }
11187 //      gameMode = EndOfGame;
11188 //      ModeHighlight();
11189
11190       /* [AS] Clear current move marker at the end of a game */
11191       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11192
11193       return FALSE;
11194     }
11195
11196     toX = moveList[currentMove][2] - AAA;
11197     toY = moveList[currentMove][3] - ONE;
11198
11199     if (moveList[currentMove][1] == '@') {
11200         if (appData.highlightLastMove) {
11201             SetHighlights(-1, -1, toX, toY);
11202         }
11203     } else {
11204         fromX = moveList[currentMove][0] - AAA;
11205         fromY = moveList[currentMove][1] - ONE;
11206
11207         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11208
11209         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11210
11211         if (appData.highlightLastMove) {
11212             SetHighlights(fromX, fromY, toX, toY);
11213         }
11214     }
11215     DisplayMove(currentMove);
11216     SendMoveToProgram(currentMove++, &first);
11217     DisplayBothClocks();
11218     DrawPosition(FALSE, boards[currentMove]);
11219     // [HGM] PV info: always display, routine tests if empty
11220     DisplayComment(currentMove - 1, commentList[currentMove]);
11221     return TRUE;
11222 }
11223
11224
11225 int
11226 LoadGameOneMove (ChessMove readAhead)
11227 {
11228     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11229     char promoChar = NULLCHAR;
11230     ChessMove moveType;
11231     char move[MSG_SIZ];
11232     char *p, *q;
11233
11234     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11235         gameMode != AnalyzeMode && gameMode != Training) {
11236         gameFileFP = NULL;
11237         return FALSE;
11238     }
11239
11240     yyboardindex = forwardMostMove;
11241     if (readAhead != EndOfFile) {
11242       moveType = readAhead;
11243     } else {
11244       if (gameFileFP == NULL)
11245           return FALSE;
11246       moveType = (ChessMove) Myylex();
11247     }
11248
11249     done = FALSE;
11250     switch (moveType) {
11251       case Comment:
11252         if (appData.debugMode)
11253           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11254         p = yy_text;
11255
11256         /* append the comment but don't display it */
11257         AppendComment(currentMove, p, FALSE);
11258         return TRUE;
11259
11260       case WhiteCapturesEnPassant:
11261       case BlackCapturesEnPassant:
11262       case WhitePromotion:
11263       case BlackPromotion:
11264       case WhiteNonPromotion:
11265       case BlackNonPromotion:
11266       case NormalMove:
11267       case WhiteKingSideCastle:
11268       case WhiteQueenSideCastle:
11269       case BlackKingSideCastle:
11270       case BlackQueenSideCastle:
11271       case WhiteKingSideCastleWild:
11272       case WhiteQueenSideCastleWild:
11273       case BlackKingSideCastleWild:
11274       case BlackQueenSideCastleWild:
11275       /* PUSH Fabien */
11276       case WhiteHSideCastleFR:
11277       case WhiteASideCastleFR:
11278       case BlackHSideCastleFR:
11279       case BlackASideCastleFR:
11280       /* POP Fabien */
11281         if (appData.debugMode)
11282           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11283         fromX = currentMoveString[0] - AAA;
11284         fromY = currentMoveString[1] - ONE;
11285         toX = currentMoveString[2] - AAA;
11286         toY = currentMoveString[3] - ONE;
11287         promoChar = currentMoveString[4];
11288         break;
11289
11290       case WhiteDrop:
11291       case BlackDrop:
11292         if (appData.debugMode)
11293           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11294         fromX = moveType == WhiteDrop ?
11295           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11296         (int) CharToPiece(ToLower(currentMoveString[0]));
11297         fromY = DROP_RANK;
11298         toX = currentMoveString[2] - AAA;
11299         toY = currentMoveString[3] - ONE;
11300         break;
11301
11302       case WhiteWins:
11303       case BlackWins:
11304       case GameIsDrawn:
11305       case GameUnfinished:
11306         if (appData.debugMode)
11307           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11308         p = strchr(yy_text, '{');
11309         if (p == NULL) p = strchr(yy_text, '(');
11310         if (p == NULL) {
11311             p = yy_text;
11312             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11313         } else {
11314             q = strchr(p, *p == '{' ? '}' : ')');
11315             if (q != NULL) *q = NULLCHAR;
11316             p++;
11317         }
11318         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11319         GameEnds(moveType, p, GE_FILE);
11320         done = TRUE;
11321         if (cmailMsgLoaded) {
11322             ClearHighlights();
11323             flipView = WhiteOnMove(currentMove);
11324             if (moveType == GameUnfinished) flipView = !flipView;
11325             if (appData.debugMode)
11326               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11327         }
11328         break;
11329
11330       case EndOfFile:
11331         if (appData.debugMode)
11332           fprintf(debugFP, "Parser hit end of file\n");
11333         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11334           case MT_NONE:
11335           case MT_CHECK:
11336             break;
11337           case MT_CHECKMATE:
11338           case MT_STAINMATE:
11339             if (WhiteOnMove(currentMove)) {
11340                 GameEnds(BlackWins, "Black mates", GE_FILE);
11341             } else {
11342                 GameEnds(WhiteWins, "White mates", GE_FILE);
11343             }
11344             break;
11345           case MT_STALEMATE:
11346             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11347             break;
11348         }
11349         done = TRUE;
11350         break;
11351
11352       case MoveNumberOne:
11353         if (lastLoadGameStart == GNUChessGame) {
11354             /* GNUChessGames have numbers, but they aren't move numbers */
11355             if (appData.debugMode)
11356               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11357                       yy_text, (int) moveType);
11358             return LoadGameOneMove(EndOfFile); /* tail recursion */
11359         }
11360         /* else fall thru */
11361
11362       case XBoardGame:
11363       case GNUChessGame:
11364       case PGNTag:
11365         /* Reached start of next game in file */
11366         if (appData.debugMode)
11367           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11368         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11369           case MT_NONE:
11370           case MT_CHECK:
11371             break;
11372           case MT_CHECKMATE:
11373           case MT_STAINMATE:
11374             if (WhiteOnMove(currentMove)) {
11375                 GameEnds(BlackWins, "Black mates", GE_FILE);
11376             } else {
11377                 GameEnds(WhiteWins, "White mates", GE_FILE);
11378             }
11379             break;
11380           case MT_STALEMATE:
11381             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11382             break;
11383         }
11384         done = TRUE;
11385         break;
11386
11387       case PositionDiagram:     /* should not happen; ignore */
11388       case ElapsedTime:         /* ignore */
11389       case NAG:                 /* ignore */
11390         if (appData.debugMode)
11391           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11392                   yy_text, (int) moveType);
11393         return LoadGameOneMove(EndOfFile); /* tail recursion */
11394
11395       case IllegalMove:
11396         if (appData.testLegality) {
11397             if (appData.debugMode)
11398               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11399             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11400                     (forwardMostMove / 2) + 1,
11401                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11402             DisplayError(move, 0);
11403             done = TRUE;
11404         } else {
11405             if (appData.debugMode)
11406               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11407                       yy_text, currentMoveString);
11408             fromX = currentMoveString[0] - AAA;
11409             fromY = currentMoveString[1] - ONE;
11410             toX = currentMoveString[2] - AAA;
11411             toY = currentMoveString[3] - ONE;
11412             promoChar = currentMoveString[4];
11413         }
11414         break;
11415
11416       case AmbiguousMove:
11417         if (appData.debugMode)
11418           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11419         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11420                 (forwardMostMove / 2) + 1,
11421                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11422         DisplayError(move, 0);
11423         done = TRUE;
11424         break;
11425
11426       default:
11427       case ImpossibleMove:
11428         if (appData.debugMode)
11429           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11430         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11431                 (forwardMostMove / 2) + 1,
11432                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11433         DisplayError(move, 0);
11434         done = TRUE;
11435         break;
11436     }
11437
11438     if (done) {
11439         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11440             DrawPosition(FALSE, boards[currentMove]);
11441             DisplayBothClocks();
11442             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11443               DisplayComment(currentMove - 1, commentList[currentMove]);
11444         }
11445         (void) StopLoadGameTimer();
11446         gameFileFP = NULL;
11447         cmailOldMove = forwardMostMove;
11448         return FALSE;
11449     } else {
11450         /* currentMoveString is set as a side-effect of yylex */
11451
11452         thinkOutput[0] = NULLCHAR;
11453         MakeMove(fromX, fromY, toX, toY, promoChar);
11454         currentMove = forwardMostMove;
11455         return TRUE;
11456     }
11457 }
11458
11459 /* Load the nth game from the given file */
11460 int
11461 LoadGameFromFile (char *filename, int n, char *title, int useList)
11462 {
11463     FILE *f;
11464     char buf[MSG_SIZ];
11465
11466     if (strcmp(filename, "-") == 0) {
11467         f = stdin;
11468         title = "stdin";
11469     } else {
11470         f = fopen(filename, "rb");
11471         if (f == NULL) {
11472           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11473             DisplayError(buf, errno);
11474             return FALSE;
11475         }
11476     }
11477     if (fseek(f, 0, 0) == -1) {
11478         /* f is not seekable; probably a pipe */
11479         useList = FALSE;
11480     }
11481     if (useList && n == 0) {
11482         int error = GameListBuild(f);
11483         if (error) {
11484             DisplayError(_("Cannot build game list"), error);
11485         } else if (!ListEmpty(&gameList) &&
11486                    ((ListGame *) gameList.tailPred)->number > 1) {
11487             GameListPopUp(f, title);
11488             return TRUE;
11489         }
11490         GameListDestroy();
11491         n = 1;
11492     }
11493     if (n == 0) n = 1;
11494     return LoadGame(f, n, title, FALSE);
11495 }
11496
11497
11498 void
11499 MakeRegisteredMove ()
11500 {
11501     int fromX, fromY, toX, toY;
11502     char promoChar;
11503     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11504         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11505           case CMAIL_MOVE:
11506           case CMAIL_DRAW:
11507             if (appData.debugMode)
11508               fprintf(debugFP, "Restoring %s for game %d\n",
11509                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11510
11511             thinkOutput[0] = NULLCHAR;
11512             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11513             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11514             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11515             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11516             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11517             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11518             MakeMove(fromX, fromY, toX, toY, promoChar);
11519             ShowMove(fromX, fromY, toX, toY);
11520
11521             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11522               case MT_NONE:
11523               case MT_CHECK:
11524                 break;
11525
11526               case MT_CHECKMATE:
11527               case MT_STAINMATE:
11528                 if (WhiteOnMove(currentMove)) {
11529                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11530                 } else {
11531                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11532                 }
11533                 break;
11534
11535               case MT_STALEMATE:
11536                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11537                 break;
11538             }
11539
11540             break;
11541
11542           case CMAIL_RESIGN:
11543             if (WhiteOnMove(currentMove)) {
11544                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11545             } else {
11546                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11547             }
11548             break;
11549
11550           case CMAIL_ACCEPT:
11551             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11552             break;
11553
11554           default:
11555             break;
11556         }
11557     }
11558
11559     return;
11560 }
11561
11562 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11563 int
11564 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11565 {
11566     int retVal;
11567
11568     if (gameNumber > nCmailGames) {
11569         DisplayError(_("No more games in this message"), 0);
11570         return FALSE;
11571     }
11572     if (f == lastLoadGameFP) {
11573         int offset = gameNumber - lastLoadGameNumber;
11574         if (offset == 0) {
11575             cmailMsg[0] = NULLCHAR;
11576             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11577                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11578                 nCmailMovesRegistered--;
11579             }
11580             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11581             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11582                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11583             }
11584         } else {
11585             if (! RegisterMove()) return FALSE;
11586         }
11587     }
11588
11589     retVal = LoadGame(f, gameNumber, title, useList);
11590
11591     /* Make move registered during previous look at this game, if any */
11592     MakeRegisteredMove();
11593
11594     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11595         commentList[currentMove]
11596           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11597         DisplayComment(currentMove - 1, commentList[currentMove]);
11598     }
11599
11600     return retVal;
11601 }
11602
11603 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11604 int
11605 ReloadGame (int offset)
11606 {
11607     int gameNumber = lastLoadGameNumber + offset;
11608     if (lastLoadGameFP == NULL) {
11609         DisplayError(_("No game has been loaded yet"), 0);
11610         return FALSE;
11611     }
11612     if (gameNumber <= 0) {
11613         DisplayError(_("Can't back up any further"), 0);
11614         return FALSE;
11615     }
11616     if (cmailMsgLoaded) {
11617         return CmailLoadGame(lastLoadGameFP, gameNumber,
11618                              lastLoadGameTitle, lastLoadGameUseList);
11619     } else {
11620         return LoadGame(lastLoadGameFP, gameNumber,
11621                         lastLoadGameTitle, lastLoadGameUseList);
11622     }
11623 }
11624
11625 int keys[EmptySquare+1];
11626
11627 int
11628 PositionMatches (Board b1, Board b2)
11629 {
11630     int r, f, sum=0;
11631     switch(appData.searchMode) {
11632         case 1: return CompareWithRights(b1, b2);
11633         case 2:
11634             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11635                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11636             }
11637             return TRUE;
11638         case 3:
11639             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11640               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11641                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11642             }
11643             return sum==0;
11644         case 4:
11645             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11646                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11647             }
11648             return sum==0;
11649     }
11650     return TRUE;
11651 }
11652
11653 #define Q_PROMO  4
11654 #define Q_EP     3
11655 #define Q_BCASTL 2
11656 #define Q_WCASTL 1
11657
11658 int pieceList[256], quickBoard[256];
11659 ChessSquare pieceType[256] = { EmptySquare };
11660 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11661 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11662 int soughtTotal, turn;
11663 Boolean epOK, flipSearch;
11664
11665 typedef struct {
11666     unsigned char piece, to;
11667 } Move;
11668
11669 #define DSIZE (250000)
11670
11671 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11672 Move *moveDatabase = initialSpace;
11673 unsigned int movePtr, dataSize = DSIZE;
11674
11675 int
11676 MakePieceList (Board board, int *counts)
11677 {
11678     int r, f, n=Q_PROMO, total=0;
11679     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11680     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11681         int sq = f + (r<<4);
11682         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11683             quickBoard[sq] = ++n;
11684             pieceList[n] = sq;
11685             pieceType[n] = board[r][f];
11686             counts[board[r][f]]++;
11687             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11688             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11689             total++;
11690         }
11691     }
11692     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11693     return total;
11694 }
11695
11696 void
11697 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11698 {
11699     int sq = fromX + (fromY<<4);
11700     int piece = quickBoard[sq];
11701     quickBoard[sq] = 0;
11702     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11703     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11704         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11705         moveDatabase[movePtr++].piece = Q_WCASTL;
11706         quickBoard[sq] = piece;
11707         piece = quickBoard[from]; quickBoard[from] = 0;
11708         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11709     } else
11710     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11711         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11712         moveDatabase[movePtr++].piece = Q_BCASTL;
11713         quickBoard[sq] = piece;
11714         piece = quickBoard[from]; quickBoard[from] = 0;
11715         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11716     } else
11717     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11718         quickBoard[(fromY<<4)+toX] = 0;
11719         moveDatabase[movePtr].piece = Q_EP;
11720         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11721         moveDatabase[movePtr].to = sq;
11722     } else
11723     if(promoPiece != pieceType[piece]) {
11724         moveDatabase[movePtr++].piece = Q_PROMO;
11725         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11726     }
11727     moveDatabase[movePtr].piece = piece;
11728     quickBoard[sq] = piece;
11729     movePtr++;
11730 }
11731
11732 int
11733 PackGame (Board board)
11734 {
11735     Move *newSpace = NULL;
11736     moveDatabase[movePtr].piece = 0; // terminate previous game
11737     if(movePtr > dataSize) {
11738         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11739         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11740         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11741         if(newSpace) {
11742             int i;
11743             Move *p = moveDatabase, *q = newSpace;
11744             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11745             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11746             moveDatabase = newSpace;
11747         } else { // calloc failed, we must be out of memory. Too bad...
11748             dataSize = 0; // prevent calloc events for all subsequent games
11749             return 0;     // and signal this one isn't cached
11750         }
11751     }
11752     movePtr++;
11753     MakePieceList(board, counts);
11754     return movePtr;
11755 }
11756
11757 int
11758 QuickCompare (Board board, int *minCounts, int *maxCounts)
11759 {   // compare according to search mode
11760     int r, f;
11761     switch(appData.searchMode)
11762     {
11763       case 1: // exact position match
11764         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11765         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11766             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11767         }
11768         break;
11769       case 2: // can have extra material on empty squares
11770         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11771             if(board[r][f] == EmptySquare) continue;
11772             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11773         }
11774         break;
11775       case 3: // material with exact Pawn structure
11776         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11777             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11778             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11779         } // fall through to material comparison
11780       case 4: // exact material
11781         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11782         break;
11783       case 6: // material range with given imbalance
11784         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11785         // fall through to range comparison
11786       case 5: // material range
11787         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11788     }
11789     return TRUE;
11790 }
11791
11792 int
11793 QuickScan (Board board, Move *move)
11794 {   // reconstruct game,and compare all positions in it
11795     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11796     do {
11797         int piece = move->piece;
11798         int to = move->to, from = pieceList[piece];
11799         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11800           if(!piece) return -1;
11801           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11802             piece = (++move)->piece;
11803             from = pieceList[piece];
11804             counts[pieceType[piece]]--;
11805             pieceType[piece] = (ChessSquare) move->to;
11806             counts[move->to]++;
11807           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11808             counts[pieceType[quickBoard[to]]]--;
11809             quickBoard[to] = 0; total--;
11810             move++;
11811             continue;
11812           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11813             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11814             from  = pieceList[piece]; // so this must be King
11815             quickBoard[from] = 0;
11816             pieceList[piece] = to;
11817             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11818             quickBoard[from] = 0; // rook
11819             quickBoard[to] = piece;
11820             to = move->to; piece = move->piece;
11821             goto aftercastle;
11822           }
11823         }
11824         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11825         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11826         quickBoard[from] = 0;
11827       aftercastle:
11828         quickBoard[to] = piece;
11829         pieceList[piece] = to;
11830         cnt++; turn ^= 3;
11831         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11832            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11833            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11834                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11835           ) {
11836             static int lastCounts[EmptySquare+1];
11837             int i;
11838             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11839             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11840         } else stretch = 0;
11841         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11842         move++;
11843     } while(1);
11844 }
11845
11846 void
11847 InitSearch ()
11848 {
11849     int r, f;
11850     flipSearch = FALSE;
11851     CopyBoard(soughtBoard, boards[currentMove]);
11852     soughtTotal = MakePieceList(soughtBoard, maxSought);
11853     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11854     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11855     CopyBoard(reverseBoard, boards[currentMove]);
11856     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11857         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11858         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11859         reverseBoard[r][f] = piece;
11860     }
11861     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11862     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11863     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11864                  || (boards[currentMove][CASTLING][2] == NoRights ||
11865                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11866                  && (boards[currentMove][CASTLING][5] == NoRights ||
11867                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11868       ) {
11869         flipSearch = TRUE;
11870         CopyBoard(flipBoard, soughtBoard);
11871         CopyBoard(rotateBoard, reverseBoard);
11872         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11873             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11874             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11875         }
11876     }
11877     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11878     if(appData.searchMode >= 5) {
11879         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11880         MakePieceList(soughtBoard, minSought);
11881         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11882     }
11883     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11884         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11885 }
11886
11887 GameInfo dummyInfo;
11888 static int creatingBook;
11889
11890 int
11891 GameContainsPosition (FILE *f, ListGame *lg)
11892 {
11893     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11894     int fromX, fromY, toX, toY;
11895     char promoChar;
11896     static int initDone=FALSE;
11897
11898     // weed out games based on numerical tag comparison
11899     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11900     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11901     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11902     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11903     if(!initDone) {
11904         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11905         initDone = TRUE;
11906     }
11907     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11908     else CopyBoard(boards[scratch], initialPosition); // default start position
11909     if(lg->moves) {
11910         turn = btm + 1;
11911         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11912         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11913     }
11914     if(btm) plyNr++;
11915     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11916     fseek(f, lg->offset, 0);
11917     yynewfile(f);
11918     while(1) {
11919         yyboardindex = scratch;
11920         quickFlag = plyNr+1;
11921         next = Myylex();
11922         quickFlag = 0;
11923         switch(next) {
11924             case PGNTag:
11925                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11926             default:
11927                 continue;
11928
11929             case XBoardGame:
11930             case GNUChessGame:
11931                 if(plyNr) return -1; // after we have seen moves, this is for new game
11932               continue;
11933
11934             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11935             case ImpossibleMove:
11936             case WhiteWins: // game ends here with these four
11937             case BlackWins:
11938             case GameIsDrawn:
11939             case GameUnfinished:
11940                 return -1;
11941
11942             case IllegalMove:
11943                 if(appData.testLegality) return -1;
11944             case WhiteCapturesEnPassant:
11945             case BlackCapturesEnPassant:
11946             case WhitePromotion:
11947             case BlackPromotion:
11948             case WhiteNonPromotion:
11949             case BlackNonPromotion:
11950             case NormalMove:
11951             case WhiteKingSideCastle:
11952             case WhiteQueenSideCastle:
11953             case BlackKingSideCastle:
11954             case BlackQueenSideCastle:
11955             case WhiteKingSideCastleWild:
11956             case WhiteQueenSideCastleWild:
11957             case BlackKingSideCastleWild:
11958             case BlackQueenSideCastleWild:
11959             case WhiteHSideCastleFR:
11960             case WhiteASideCastleFR:
11961             case BlackHSideCastleFR:
11962             case BlackASideCastleFR:
11963                 fromX = currentMoveString[0] - AAA;
11964                 fromY = currentMoveString[1] - ONE;
11965                 toX = currentMoveString[2] - AAA;
11966                 toY = currentMoveString[3] - ONE;
11967                 promoChar = currentMoveString[4];
11968                 break;
11969             case WhiteDrop:
11970             case BlackDrop:
11971                 fromX = next == WhiteDrop ?
11972                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11973                   (int) CharToPiece(ToLower(currentMoveString[0]));
11974                 fromY = DROP_RANK;
11975                 toX = currentMoveString[2] - AAA;
11976                 toY = currentMoveString[3] - ONE;
11977                 promoChar = 0;
11978                 break;
11979         }
11980         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11981         plyNr++;
11982         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11983         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11984         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11985         if(appData.findMirror) {
11986             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11987             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11988         }
11989     }
11990 }
11991
11992 /* Load the nth game from open file f */
11993 int
11994 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11995 {
11996     ChessMove cm;
11997     char buf[MSG_SIZ];
11998     int gn = gameNumber;
11999     ListGame *lg = NULL;
12000     int numPGNTags = 0;
12001     int err, pos = -1;
12002     GameMode oldGameMode;
12003     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12004
12005     if (appData.debugMode)
12006         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12007
12008     if (gameMode == Training )
12009         SetTrainingModeOff();
12010
12011     oldGameMode = gameMode;
12012     if (gameMode != BeginningOfGame) {
12013       Reset(FALSE, TRUE);
12014     }
12015
12016     gameFileFP = f;
12017     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12018         fclose(lastLoadGameFP);
12019     }
12020
12021     if (useList) {
12022         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12023
12024         if (lg) {
12025             fseek(f, lg->offset, 0);
12026             GameListHighlight(gameNumber);
12027             pos = lg->position;
12028             gn = 1;
12029         }
12030         else {
12031             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12032               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12033             else
12034             DisplayError(_("Game number out of range"), 0);
12035             return FALSE;
12036         }
12037     } else {
12038         GameListDestroy();
12039         if (fseek(f, 0, 0) == -1) {
12040             if (f == lastLoadGameFP ?
12041                 gameNumber == lastLoadGameNumber + 1 :
12042                 gameNumber == 1) {
12043                 gn = 1;
12044             } else {
12045                 DisplayError(_("Can't seek on game file"), 0);
12046                 return FALSE;
12047             }
12048         }
12049     }
12050     lastLoadGameFP = f;
12051     lastLoadGameNumber = gameNumber;
12052     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12053     lastLoadGameUseList = useList;
12054
12055     yynewfile(f);
12056
12057     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12058       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12059                 lg->gameInfo.black);
12060             DisplayTitle(buf);
12061     } else if (*title != NULLCHAR) {
12062         if (gameNumber > 1) {
12063           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12064             DisplayTitle(buf);
12065         } else {
12066             DisplayTitle(title);
12067         }
12068     }
12069
12070     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12071         gameMode = PlayFromGameFile;
12072         ModeHighlight();
12073     }
12074
12075     currentMove = forwardMostMove = backwardMostMove = 0;
12076     CopyBoard(boards[0], initialPosition);
12077     StopClocks();
12078
12079     /*
12080      * Skip the first gn-1 games in the file.
12081      * Also skip over anything that precedes an identifiable
12082      * start of game marker, to avoid being confused by
12083      * garbage at the start of the file.  Currently
12084      * recognized start of game markers are the move number "1",
12085      * the pattern "gnuchess .* game", the pattern
12086      * "^[#;%] [^ ]* game file", and a PGN tag block.
12087      * A game that starts with one of the latter two patterns
12088      * will also have a move number 1, possibly
12089      * following a position diagram.
12090      * 5-4-02: Let's try being more lenient and allowing a game to
12091      * start with an unnumbered move.  Does that break anything?
12092      */
12093     cm = lastLoadGameStart = EndOfFile;
12094     while (gn > 0) {
12095         yyboardindex = forwardMostMove;
12096         cm = (ChessMove) Myylex();
12097         switch (cm) {
12098           case EndOfFile:
12099             if (cmailMsgLoaded) {
12100                 nCmailGames = CMAIL_MAX_GAMES - gn;
12101             } else {
12102                 Reset(TRUE, TRUE);
12103                 DisplayError(_("Game not found in file"), 0);
12104             }
12105             return FALSE;
12106
12107           case GNUChessGame:
12108           case XBoardGame:
12109             gn--;
12110             lastLoadGameStart = cm;
12111             break;
12112
12113           case MoveNumberOne:
12114             switch (lastLoadGameStart) {
12115               case GNUChessGame:
12116               case XBoardGame:
12117               case PGNTag:
12118                 break;
12119               case MoveNumberOne:
12120               case EndOfFile:
12121                 gn--;           /* count this game */
12122                 lastLoadGameStart = cm;
12123                 break;
12124               default:
12125                 /* impossible */
12126                 break;
12127             }
12128             break;
12129
12130           case PGNTag:
12131             switch (lastLoadGameStart) {
12132               case GNUChessGame:
12133               case PGNTag:
12134               case MoveNumberOne:
12135               case EndOfFile:
12136                 gn--;           /* count this game */
12137                 lastLoadGameStart = cm;
12138                 break;
12139               case XBoardGame:
12140                 lastLoadGameStart = cm; /* game counted already */
12141                 break;
12142               default:
12143                 /* impossible */
12144                 break;
12145             }
12146             if (gn > 0) {
12147                 do {
12148                     yyboardindex = forwardMostMove;
12149                     cm = (ChessMove) Myylex();
12150                 } while (cm == PGNTag || cm == Comment);
12151             }
12152             break;
12153
12154           case WhiteWins:
12155           case BlackWins:
12156           case GameIsDrawn:
12157             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12158                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12159                     != CMAIL_OLD_RESULT) {
12160                     nCmailResults ++ ;
12161                     cmailResult[  CMAIL_MAX_GAMES
12162                                 - gn - 1] = CMAIL_OLD_RESULT;
12163                 }
12164             }
12165             break;
12166
12167           case NormalMove:
12168             /* Only a NormalMove can be at the start of a game
12169              * without a position diagram. */
12170             if (lastLoadGameStart == EndOfFile ) {
12171               gn--;
12172               lastLoadGameStart = MoveNumberOne;
12173             }
12174             break;
12175
12176           default:
12177             break;
12178         }
12179     }
12180
12181     if (appData.debugMode)
12182       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12183
12184     if (cm == XBoardGame) {
12185         /* Skip any header junk before position diagram and/or move 1 */
12186         for (;;) {
12187             yyboardindex = forwardMostMove;
12188             cm = (ChessMove) Myylex();
12189
12190             if (cm == EndOfFile ||
12191                 cm == GNUChessGame || cm == XBoardGame) {
12192                 /* Empty game; pretend end-of-file and handle later */
12193                 cm = EndOfFile;
12194                 break;
12195             }
12196
12197             if (cm == MoveNumberOne || cm == PositionDiagram ||
12198                 cm == PGNTag || cm == Comment)
12199               break;
12200         }
12201     } else if (cm == GNUChessGame) {
12202         if (gameInfo.event != NULL) {
12203             free(gameInfo.event);
12204         }
12205         gameInfo.event = StrSave(yy_text);
12206     }
12207
12208     startedFromSetupPosition = FALSE;
12209     while (cm == PGNTag) {
12210         if (appData.debugMode)
12211           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12212         err = ParsePGNTag(yy_text, &gameInfo);
12213         if (!err) numPGNTags++;
12214
12215         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12216         if(gameInfo.variant != oldVariant) {
12217             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12218             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12219             InitPosition(TRUE);
12220             oldVariant = gameInfo.variant;
12221             if (appData.debugMode)
12222               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12223         }
12224
12225
12226         if (gameInfo.fen != NULL) {
12227           Board initial_position;
12228           startedFromSetupPosition = TRUE;
12229           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12230             Reset(TRUE, TRUE);
12231             DisplayError(_("Bad FEN position in file"), 0);
12232             return FALSE;
12233           }
12234           CopyBoard(boards[0], initial_position);
12235           if (blackPlaysFirst) {
12236             currentMove = forwardMostMove = backwardMostMove = 1;
12237             CopyBoard(boards[1], initial_position);
12238             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12239             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12240             timeRemaining[0][1] = whiteTimeRemaining;
12241             timeRemaining[1][1] = blackTimeRemaining;
12242             if (commentList[0] != NULL) {
12243               commentList[1] = commentList[0];
12244               commentList[0] = NULL;
12245             }
12246           } else {
12247             currentMove = forwardMostMove = backwardMostMove = 0;
12248           }
12249           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12250           {   int i;
12251               initialRulePlies = FENrulePlies;
12252               for( i=0; i< nrCastlingRights; i++ )
12253                   initialRights[i] = initial_position[CASTLING][i];
12254           }
12255           yyboardindex = forwardMostMove;
12256           free(gameInfo.fen);
12257           gameInfo.fen = NULL;
12258         }
12259
12260         yyboardindex = forwardMostMove;
12261         cm = (ChessMove) Myylex();
12262
12263         /* Handle comments interspersed among the tags */
12264         while (cm == Comment) {
12265             char *p;
12266             if (appData.debugMode)
12267               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12268             p = yy_text;
12269             AppendComment(currentMove, p, FALSE);
12270             yyboardindex = forwardMostMove;
12271             cm = (ChessMove) Myylex();
12272         }
12273     }
12274
12275     /* don't rely on existence of Event tag since if game was
12276      * pasted from clipboard the Event tag may not exist
12277      */
12278     if (numPGNTags > 0){
12279         char *tags;
12280         if (gameInfo.variant == VariantNormal) {
12281           VariantClass v = StringToVariant(gameInfo.event);
12282           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12283           if(v < VariantShogi) gameInfo.variant = v;
12284         }
12285         if (!matchMode) {
12286           if( appData.autoDisplayTags ) {
12287             tags = PGNTags(&gameInfo);
12288             TagsPopUp(tags, CmailMsg());
12289             free(tags);
12290           }
12291         }
12292     } else {
12293         /* Make something up, but don't display it now */
12294         SetGameInfo();
12295         TagsPopDown();
12296     }
12297
12298     if (cm == PositionDiagram) {
12299         int i, j;
12300         char *p;
12301         Board initial_position;
12302
12303         if (appData.debugMode)
12304           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12305
12306         if (!startedFromSetupPosition) {
12307             p = yy_text;
12308             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12309               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12310                 switch (*p) {
12311                   case '{':
12312                   case '[':
12313                   case '-':
12314                   case ' ':
12315                   case '\t':
12316                   case '\n':
12317                   case '\r':
12318                     break;
12319                   default:
12320                     initial_position[i][j++] = CharToPiece(*p);
12321                     break;
12322                 }
12323             while (*p == ' ' || *p == '\t' ||
12324                    *p == '\n' || *p == '\r') p++;
12325
12326             if (strncmp(p, "black", strlen("black"))==0)
12327               blackPlaysFirst = TRUE;
12328             else
12329               blackPlaysFirst = FALSE;
12330             startedFromSetupPosition = TRUE;
12331
12332             CopyBoard(boards[0], initial_position);
12333             if (blackPlaysFirst) {
12334                 currentMove = forwardMostMove = backwardMostMove = 1;
12335                 CopyBoard(boards[1], initial_position);
12336                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12337                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12338                 timeRemaining[0][1] = whiteTimeRemaining;
12339                 timeRemaining[1][1] = blackTimeRemaining;
12340                 if (commentList[0] != NULL) {
12341                     commentList[1] = commentList[0];
12342                     commentList[0] = NULL;
12343                 }
12344             } else {
12345                 currentMove = forwardMostMove = backwardMostMove = 0;
12346             }
12347         }
12348         yyboardindex = forwardMostMove;
12349         cm = (ChessMove) Myylex();
12350     }
12351
12352   if(!creatingBook) {
12353     if (first.pr == NoProc) {
12354         StartChessProgram(&first);
12355     }
12356     InitChessProgram(&first, FALSE);
12357     SendToProgram("force\n", &first);
12358     if (startedFromSetupPosition) {
12359         SendBoard(&first, forwardMostMove);
12360     if (appData.debugMode) {
12361         fprintf(debugFP, "Load Game\n");
12362     }
12363         DisplayBothClocks();
12364     }
12365   }
12366
12367     /* [HGM] server: flag to write setup moves in broadcast file as one */
12368     loadFlag = appData.suppressLoadMoves;
12369
12370     while (cm == Comment) {
12371         char *p;
12372         if (appData.debugMode)
12373           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12374         p = yy_text;
12375         AppendComment(currentMove, p, FALSE);
12376         yyboardindex = forwardMostMove;
12377         cm = (ChessMove) Myylex();
12378     }
12379
12380     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12381         cm == WhiteWins || cm == BlackWins ||
12382         cm == GameIsDrawn || cm == GameUnfinished) {
12383         DisplayMessage("", _("No moves in game"));
12384         if (cmailMsgLoaded) {
12385             if (appData.debugMode)
12386               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12387             ClearHighlights();
12388             flipView = FALSE;
12389         }
12390         DrawPosition(FALSE, boards[currentMove]);
12391         DisplayBothClocks();
12392         gameMode = EditGame;
12393         ModeHighlight();
12394         gameFileFP = NULL;
12395         cmailOldMove = 0;
12396         return TRUE;
12397     }
12398
12399     // [HGM] PV info: routine tests if comment empty
12400     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12401         DisplayComment(currentMove - 1, commentList[currentMove]);
12402     }
12403     if (!matchMode && appData.timeDelay != 0)
12404       DrawPosition(FALSE, boards[currentMove]);
12405
12406     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12407       programStats.ok_to_send = 1;
12408     }
12409
12410     /* if the first token after the PGN tags is a move
12411      * and not move number 1, retrieve it from the parser
12412      */
12413     if (cm != MoveNumberOne)
12414         LoadGameOneMove(cm);
12415
12416     /* load the remaining moves from the file */
12417     while (LoadGameOneMove(EndOfFile)) {
12418       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12419       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12420     }
12421
12422     /* rewind to the start of the game */
12423     currentMove = backwardMostMove;
12424
12425     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12426
12427     if (oldGameMode == AnalyzeFile ||
12428         oldGameMode == AnalyzeMode) {
12429       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12430       AnalyzeFileEvent();
12431     }
12432
12433     if(creatingBook) return TRUE;
12434     if (!matchMode && pos > 0) {
12435         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12436     } else
12437     if (matchMode || appData.timeDelay == 0) {
12438       ToEndEvent();
12439     } else if (appData.timeDelay > 0) {
12440       AutoPlayGameLoop();
12441     }
12442
12443     if (appData.debugMode)
12444         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12445
12446     loadFlag = 0; /* [HGM] true game starts */
12447     return TRUE;
12448 }
12449
12450 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12451 int
12452 ReloadPosition (int offset)
12453 {
12454     int positionNumber = lastLoadPositionNumber + offset;
12455     if (lastLoadPositionFP == NULL) {
12456         DisplayError(_("No position has been loaded yet"), 0);
12457         return FALSE;
12458     }
12459     if (positionNumber <= 0) {
12460         DisplayError(_("Can't back up any further"), 0);
12461         return FALSE;
12462     }
12463     return LoadPosition(lastLoadPositionFP, positionNumber,
12464                         lastLoadPositionTitle);
12465 }
12466
12467 /* Load the nth position from the given file */
12468 int
12469 LoadPositionFromFile (char *filename, int n, char *title)
12470 {
12471     FILE *f;
12472     char buf[MSG_SIZ];
12473
12474     if (strcmp(filename, "-") == 0) {
12475         return LoadPosition(stdin, n, "stdin");
12476     } else {
12477         f = fopen(filename, "rb");
12478         if (f == NULL) {
12479             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12480             DisplayError(buf, errno);
12481             return FALSE;
12482         } else {
12483             return LoadPosition(f, n, title);
12484         }
12485     }
12486 }
12487
12488 /* Load the nth position from the given open file, and close it */
12489 int
12490 LoadPosition (FILE *f, int positionNumber, char *title)
12491 {
12492     char *p, line[MSG_SIZ];
12493     Board initial_position;
12494     int i, j, fenMode, pn;
12495
12496     if (gameMode == Training )
12497         SetTrainingModeOff();
12498
12499     if (gameMode != BeginningOfGame) {
12500         Reset(FALSE, TRUE);
12501     }
12502     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12503         fclose(lastLoadPositionFP);
12504     }
12505     if (positionNumber == 0) positionNumber = 1;
12506     lastLoadPositionFP = f;
12507     lastLoadPositionNumber = positionNumber;
12508     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12509     if (first.pr == NoProc && !appData.noChessProgram) {
12510       StartChessProgram(&first);
12511       InitChessProgram(&first, FALSE);
12512     }
12513     pn = positionNumber;
12514     if (positionNumber < 0) {
12515         /* Negative position number means to seek to that byte offset */
12516         if (fseek(f, -positionNumber, 0) == -1) {
12517             DisplayError(_("Can't seek on position file"), 0);
12518             return FALSE;
12519         };
12520         pn = 1;
12521     } else {
12522         if (fseek(f, 0, 0) == -1) {
12523             if (f == lastLoadPositionFP ?
12524                 positionNumber == lastLoadPositionNumber + 1 :
12525                 positionNumber == 1) {
12526                 pn = 1;
12527             } else {
12528                 DisplayError(_("Can't seek on position file"), 0);
12529                 return FALSE;
12530             }
12531         }
12532     }
12533     /* See if this file is FEN or old-style xboard */
12534     if (fgets(line, MSG_SIZ, f) == NULL) {
12535         DisplayError(_("Position not found in file"), 0);
12536         return FALSE;
12537     }
12538     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12539     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12540
12541     if (pn >= 2) {
12542         if (fenMode || line[0] == '#') pn--;
12543         while (pn > 0) {
12544             /* skip positions before number pn */
12545             if (fgets(line, MSG_SIZ, f) == NULL) {
12546                 Reset(TRUE, TRUE);
12547                 DisplayError(_("Position not found in file"), 0);
12548                 return FALSE;
12549             }
12550             if (fenMode || line[0] == '#') pn--;
12551         }
12552     }
12553
12554     if (fenMode) {
12555         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12556             DisplayError(_("Bad FEN position in file"), 0);
12557             return FALSE;
12558         }
12559     } else {
12560         (void) fgets(line, MSG_SIZ, f);
12561         (void) fgets(line, MSG_SIZ, f);
12562
12563         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12564             (void) fgets(line, MSG_SIZ, f);
12565             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12566                 if (*p == ' ')
12567                   continue;
12568                 initial_position[i][j++] = CharToPiece(*p);
12569             }
12570         }
12571
12572         blackPlaysFirst = FALSE;
12573         if (!feof(f)) {
12574             (void) fgets(line, MSG_SIZ, f);
12575             if (strncmp(line, "black", strlen("black"))==0)
12576               blackPlaysFirst = TRUE;
12577         }
12578     }
12579     startedFromSetupPosition = TRUE;
12580
12581     CopyBoard(boards[0], initial_position);
12582     if (blackPlaysFirst) {
12583         currentMove = forwardMostMove = backwardMostMove = 1;
12584         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12585         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12586         CopyBoard(boards[1], initial_position);
12587         DisplayMessage("", _("Black to play"));
12588     } else {
12589         currentMove = forwardMostMove = backwardMostMove = 0;
12590         DisplayMessage("", _("White to play"));
12591     }
12592     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12593     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12594         SendToProgram("force\n", &first);
12595         SendBoard(&first, forwardMostMove);
12596     }
12597     if (appData.debugMode) {
12598 int i, j;
12599   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12600   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12601         fprintf(debugFP, "Load Position\n");
12602     }
12603
12604     if (positionNumber > 1) {
12605       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12606         DisplayTitle(line);
12607     } else {
12608         DisplayTitle(title);
12609     }
12610     gameMode = EditGame;
12611     ModeHighlight();
12612     ResetClocks();
12613     timeRemaining[0][1] = whiteTimeRemaining;
12614     timeRemaining[1][1] = blackTimeRemaining;
12615     DrawPosition(FALSE, boards[currentMove]);
12616
12617     return TRUE;
12618 }
12619
12620
12621 void
12622 CopyPlayerNameIntoFileName (char **dest, char *src)
12623 {
12624     while (*src != NULLCHAR && *src != ',') {
12625         if (*src == ' ') {
12626             *(*dest)++ = '_';
12627             src++;
12628         } else {
12629             *(*dest)++ = *src++;
12630         }
12631     }
12632 }
12633
12634 char *
12635 DefaultFileName (char *ext)
12636 {
12637     static char def[MSG_SIZ];
12638     char *p;
12639
12640     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12641         p = def;
12642         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12643         *p++ = '-';
12644         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12645         *p++ = '.';
12646         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12647     } else {
12648         def[0] = NULLCHAR;
12649     }
12650     return def;
12651 }
12652
12653 /* Save the current game to the given file */
12654 int
12655 SaveGameToFile (char *filename, int append)
12656 {
12657     FILE *f;
12658     char buf[MSG_SIZ];
12659     int result, i, t,tot=0;
12660
12661     if (strcmp(filename, "-") == 0) {
12662         return SaveGame(stdout, 0, NULL);
12663     } else {
12664         for(i=0; i<10; i++) { // upto 10 tries
12665              f = fopen(filename, append ? "a" : "w");
12666              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12667              if(f || errno != 13) break;
12668              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12669              tot += t;
12670         }
12671         if (f == NULL) {
12672             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12673             DisplayError(buf, errno);
12674             return FALSE;
12675         } else {
12676             safeStrCpy(buf, lastMsg, MSG_SIZ);
12677             DisplayMessage(_("Waiting for access to save file"), "");
12678             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12679             DisplayMessage(_("Saving game"), "");
12680             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12681             result = SaveGame(f, 0, NULL);
12682             DisplayMessage(buf, "");
12683             return result;
12684         }
12685     }
12686 }
12687
12688 char *
12689 SavePart (char *str)
12690 {
12691     static char buf[MSG_SIZ];
12692     char *p;
12693
12694     p = strchr(str, ' ');
12695     if (p == NULL) return str;
12696     strncpy(buf, str, p - str);
12697     buf[p - str] = NULLCHAR;
12698     return buf;
12699 }
12700
12701 #define PGN_MAX_LINE 75
12702
12703 #define PGN_SIDE_WHITE  0
12704 #define PGN_SIDE_BLACK  1
12705
12706 static int
12707 FindFirstMoveOutOfBook (int side)
12708 {
12709     int result = -1;
12710
12711     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12712         int index = backwardMostMove;
12713         int has_book_hit = 0;
12714
12715         if( (index % 2) != side ) {
12716             index++;
12717         }
12718
12719         while( index < forwardMostMove ) {
12720             /* Check to see if engine is in book */
12721             int depth = pvInfoList[index].depth;
12722             int score = pvInfoList[index].score;
12723             int in_book = 0;
12724
12725             if( depth <= 2 ) {
12726                 in_book = 1;
12727             }
12728             else if( score == 0 && depth == 63 ) {
12729                 in_book = 1; /* Zappa */
12730             }
12731             else if( score == 2 && depth == 99 ) {
12732                 in_book = 1; /* Abrok */
12733             }
12734
12735             has_book_hit += in_book;
12736
12737             if( ! in_book ) {
12738                 result = index;
12739
12740                 break;
12741             }
12742
12743             index += 2;
12744         }
12745     }
12746
12747     return result;
12748 }
12749
12750 void
12751 GetOutOfBookInfo (char * buf)
12752 {
12753     int oob[2];
12754     int i;
12755     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12756
12757     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12758     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12759
12760     *buf = '\0';
12761
12762     if( oob[0] >= 0 || oob[1] >= 0 ) {
12763         for( i=0; i<2; i++ ) {
12764             int idx = oob[i];
12765
12766             if( idx >= 0 ) {
12767                 if( i > 0 && oob[0] >= 0 ) {
12768                     strcat( buf, "   " );
12769                 }
12770
12771                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12772                 sprintf( buf+strlen(buf), "%s%.2f",
12773                     pvInfoList[idx].score >= 0 ? "+" : "",
12774                     pvInfoList[idx].score / 100.0 );
12775             }
12776         }
12777     }
12778 }
12779
12780 /* Save game in PGN style and close the file */
12781 int
12782 SaveGamePGN (FILE *f)
12783 {
12784     int i, offset, linelen, newblock;
12785 //    char *movetext;
12786     char numtext[32];
12787     int movelen, numlen, blank;
12788     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12789
12790     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12791
12792     PrintPGNTags(f, &gameInfo);
12793
12794     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12795
12796     if (backwardMostMove > 0 || startedFromSetupPosition) {
12797         char *fen = PositionToFEN(backwardMostMove, NULL);
12798         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12799         fprintf(f, "\n{--------------\n");
12800         PrintPosition(f, backwardMostMove);
12801         fprintf(f, "--------------}\n");
12802         free(fen);
12803     }
12804     else {
12805         /* [AS] Out of book annotation */
12806         if( appData.saveOutOfBookInfo ) {
12807             char buf[64];
12808
12809             GetOutOfBookInfo( buf );
12810
12811             if( buf[0] != '\0' ) {
12812                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12813             }
12814         }
12815
12816         fprintf(f, "\n");
12817     }
12818
12819     i = backwardMostMove;
12820     linelen = 0;
12821     newblock = TRUE;
12822
12823     while (i < forwardMostMove) {
12824         /* Print comments preceding this move */
12825         if (commentList[i] != NULL) {
12826             if (linelen > 0) fprintf(f, "\n");
12827             fprintf(f, "%s", commentList[i]);
12828             linelen = 0;
12829             newblock = TRUE;
12830         }
12831
12832         /* Format move number */
12833         if ((i % 2) == 0)
12834           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12835         else
12836           if (newblock)
12837             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12838           else
12839             numtext[0] = NULLCHAR;
12840
12841         numlen = strlen(numtext);
12842         newblock = FALSE;
12843
12844         /* Print move number */
12845         blank = linelen > 0 && numlen > 0;
12846         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12847             fprintf(f, "\n");
12848             linelen = 0;
12849             blank = 0;
12850         }
12851         if (blank) {
12852             fprintf(f, " ");
12853             linelen++;
12854         }
12855         fprintf(f, "%s", numtext);
12856         linelen += numlen;
12857
12858         /* Get move */
12859         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12860         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12861
12862         /* Print move */
12863         blank = linelen > 0 && movelen > 0;
12864         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12865             fprintf(f, "\n");
12866             linelen = 0;
12867             blank = 0;
12868         }
12869         if (blank) {
12870             fprintf(f, " ");
12871             linelen++;
12872         }
12873         fprintf(f, "%s", move_buffer);
12874         linelen += movelen;
12875
12876         /* [AS] Add PV info if present */
12877         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12878             /* [HGM] add time */
12879             char buf[MSG_SIZ]; int seconds;
12880
12881             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12882
12883             if( seconds <= 0)
12884               buf[0] = 0;
12885             else
12886               if( seconds < 30 )
12887                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12888               else
12889                 {
12890                   seconds = (seconds + 4)/10; // round to full seconds
12891                   if( seconds < 60 )
12892                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12893                   else
12894                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12895                 }
12896
12897             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12898                       pvInfoList[i].score >= 0 ? "+" : "",
12899                       pvInfoList[i].score / 100.0,
12900                       pvInfoList[i].depth,
12901                       buf );
12902
12903             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12904
12905             /* Print score/depth */
12906             blank = linelen > 0 && movelen > 0;
12907             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12908                 fprintf(f, "\n");
12909                 linelen = 0;
12910                 blank = 0;
12911             }
12912             if (blank) {
12913                 fprintf(f, " ");
12914                 linelen++;
12915             }
12916             fprintf(f, "%s", move_buffer);
12917             linelen += movelen;
12918         }
12919
12920         i++;
12921     }
12922
12923     /* Start a new line */
12924     if (linelen > 0) fprintf(f, "\n");
12925
12926     /* Print comments after last move */
12927     if (commentList[i] != NULL) {
12928         fprintf(f, "%s\n", commentList[i]);
12929     }
12930
12931     /* Print result */
12932     if (gameInfo.resultDetails != NULL &&
12933         gameInfo.resultDetails[0] != NULLCHAR) {
12934         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12935                 PGNResult(gameInfo.result));
12936     } else {
12937         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12938     }
12939
12940     fclose(f);
12941     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12942     return TRUE;
12943 }
12944
12945 /* Save game in old style and close the file */
12946 int
12947 SaveGameOldStyle (FILE *f)
12948 {
12949     int i, offset;
12950     time_t tm;
12951
12952     tm = time((time_t *) NULL);
12953
12954     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12955     PrintOpponents(f);
12956
12957     if (backwardMostMove > 0 || startedFromSetupPosition) {
12958         fprintf(f, "\n[--------------\n");
12959         PrintPosition(f, backwardMostMove);
12960         fprintf(f, "--------------]\n");
12961     } else {
12962         fprintf(f, "\n");
12963     }
12964
12965     i = backwardMostMove;
12966     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12967
12968     while (i < forwardMostMove) {
12969         if (commentList[i] != NULL) {
12970             fprintf(f, "[%s]\n", commentList[i]);
12971         }
12972
12973         if ((i % 2) == 1) {
12974             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12975             i++;
12976         } else {
12977             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12978             i++;
12979             if (commentList[i] != NULL) {
12980                 fprintf(f, "\n");
12981                 continue;
12982             }
12983             if (i >= forwardMostMove) {
12984                 fprintf(f, "\n");
12985                 break;
12986             }
12987             fprintf(f, "%s\n", parseList[i]);
12988             i++;
12989         }
12990     }
12991
12992     if (commentList[i] != NULL) {
12993         fprintf(f, "[%s]\n", commentList[i]);
12994     }
12995
12996     /* This isn't really the old style, but it's close enough */
12997     if (gameInfo.resultDetails != NULL &&
12998         gameInfo.resultDetails[0] != NULLCHAR) {
12999         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13000                 gameInfo.resultDetails);
13001     } else {
13002         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13003     }
13004
13005     fclose(f);
13006     return TRUE;
13007 }
13008
13009 /* Save the current game to open file f and close the file */
13010 int
13011 SaveGame (FILE *f, int dummy, char *dummy2)
13012 {
13013     if (gameMode == EditPosition) EditPositionDone(TRUE);
13014     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13015     if (appData.oldSaveStyle)
13016       return SaveGameOldStyle(f);
13017     else
13018       return SaveGamePGN(f);
13019 }
13020
13021 /* Save the current position to the given file */
13022 int
13023 SavePositionToFile (char *filename)
13024 {
13025     FILE *f;
13026     char buf[MSG_SIZ];
13027
13028     if (strcmp(filename, "-") == 0) {
13029         return SavePosition(stdout, 0, NULL);
13030     } else {
13031         f = fopen(filename, "a");
13032         if (f == NULL) {
13033             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13034             DisplayError(buf, errno);
13035             return FALSE;
13036         } else {
13037             safeStrCpy(buf, lastMsg, MSG_SIZ);
13038             DisplayMessage(_("Waiting for access to save file"), "");
13039             flock(fileno(f), LOCK_EX); // [HGM] lock
13040             DisplayMessage(_("Saving position"), "");
13041             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13042             SavePosition(f, 0, NULL);
13043             DisplayMessage(buf, "");
13044             return TRUE;
13045         }
13046     }
13047 }
13048
13049 /* Save the current position to the given open file and close the file */
13050 int
13051 SavePosition (FILE *f, int dummy, char *dummy2)
13052 {
13053     time_t tm;
13054     char *fen;
13055
13056     if (gameMode == EditPosition) EditPositionDone(TRUE);
13057     if (appData.oldSaveStyle) {
13058         tm = time((time_t *) NULL);
13059
13060         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13061         PrintOpponents(f);
13062         fprintf(f, "[--------------\n");
13063         PrintPosition(f, currentMove);
13064         fprintf(f, "--------------]\n");
13065     } else {
13066         fen = PositionToFEN(currentMove, NULL);
13067         fprintf(f, "%s\n", fen);
13068         free(fen);
13069     }
13070     fclose(f);
13071     return TRUE;
13072 }
13073
13074 void
13075 ReloadCmailMsgEvent (int unregister)
13076 {
13077 #if !WIN32
13078     static char *inFilename = NULL;
13079     static char *outFilename;
13080     int i;
13081     struct stat inbuf, outbuf;
13082     int status;
13083
13084     /* Any registered moves are unregistered if unregister is set, */
13085     /* i.e. invoked by the signal handler */
13086     if (unregister) {
13087         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13088             cmailMoveRegistered[i] = FALSE;
13089             if (cmailCommentList[i] != NULL) {
13090                 free(cmailCommentList[i]);
13091                 cmailCommentList[i] = NULL;
13092             }
13093         }
13094         nCmailMovesRegistered = 0;
13095     }
13096
13097     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13098         cmailResult[i] = CMAIL_NOT_RESULT;
13099     }
13100     nCmailResults = 0;
13101
13102     if (inFilename == NULL) {
13103         /* Because the filenames are static they only get malloced once  */
13104         /* and they never get freed                                      */
13105         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13106         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13107
13108         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13109         sprintf(outFilename, "%s.out", appData.cmailGameName);
13110     }
13111
13112     status = stat(outFilename, &outbuf);
13113     if (status < 0) {
13114         cmailMailedMove = FALSE;
13115     } else {
13116         status = stat(inFilename, &inbuf);
13117         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13118     }
13119
13120     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13121        counts the games, notes how each one terminated, etc.
13122
13123        It would be nice to remove this kludge and instead gather all
13124        the information while building the game list.  (And to keep it
13125        in the game list nodes instead of having a bunch of fixed-size
13126        parallel arrays.)  Note this will require getting each game's
13127        termination from the PGN tags, as the game list builder does
13128        not process the game moves.  --mann
13129        */
13130     cmailMsgLoaded = TRUE;
13131     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13132
13133     /* Load first game in the file or popup game menu */
13134     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13135
13136 #endif /* !WIN32 */
13137     return;
13138 }
13139
13140 int
13141 RegisterMove ()
13142 {
13143     FILE *f;
13144     char string[MSG_SIZ];
13145
13146     if (   cmailMailedMove
13147         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13148         return TRUE;            /* Allow free viewing  */
13149     }
13150
13151     /* Unregister move to ensure that we don't leave RegisterMove        */
13152     /* with the move registered when the conditions for registering no   */
13153     /* longer hold                                                       */
13154     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13155         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13156         nCmailMovesRegistered --;
13157
13158         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13159           {
13160               free(cmailCommentList[lastLoadGameNumber - 1]);
13161               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13162           }
13163     }
13164
13165     if (cmailOldMove == -1) {
13166         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13167         return FALSE;
13168     }
13169
13170     if (currentMove > cmailOldMove + 1) {
13171         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13172         return FALSE;
13173     }
13174
13175     if (currentMove < cmailOldMove) {
13176         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13177         return FALSE;
13178     }
13179
13180     if (forwardMostMove > currentMove) {
13181         /* Silently truncate extra moves */
13182         TruncateGame();
13183     }
13184
13185     if (   (currentMove == cmailOldMove + 1)
13186         || (   (currentMove == cmailOldMove)
13187             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13188                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13189         if (gameInfo.result != GameUnfinished) {
13190             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13191         }
13192
13193         if (commentList[currentMove] != NULL) {
13194             cmailCommentList[lastLoadGameNumber - 1]
13195               = StrSave(commentList[currentMove]);
13196         }
13197         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13198
13199         if (appData.debugMode)
13200           fprintf(debugFP, "Saving %s for game %d\n",
13201                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13202
13203         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13204
13205         f = fopen(string, "w");
13206         if (appData.oldSaveStyle) {
13207             SaveGameOldStyle(f); /* also closes the file */
13208
13209             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13210             f = fopen(string, "w");
13211             SavePosition(f, 0, NULL); /* also closes the file */
13212         } else {
13213             fprintf(f, "{--------------\n");
13214             PrintPosition(f, currentMove);
13215             fprintf(f, "--------------}\n\n");
13216
13217             SaveGame(f, 0, NULL); /* also closes the file*/
13218         }
13219
13220         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13221         nCmailMovesRegistered ++;
13222     } else if (nCmailGames == 1) {
13223         DisplayError(_("You have not made a move yet"), 0);
13224         return FALSE;
13225     }
13226
13227     return TRUE;
13228 }
13229
13230 void
13231 MailMoveEvent ()
13232 {
13233 #if !WIN32
13234     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13235     FILE *commandOutput;
13236     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13237     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13238     int nBuffers;
13239     int i;
13240     int archived;
13241     char *arcDir;
13242
13243     if (! cmailMsgLoaded) {
13244         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13245         return;
13246     }
13247
13248     if (nCmailGames == nCmailResults) {
13249         DisplayError(_("No unfinished games"), 0);
13250         return;
13251     }
13252
13253 #if CMAIL_PROHIBIT_REMAIL
13254     if (cmailMailedMove) {
13255       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);
13256         DisplayError(msg, 0);
13257         return;
13258     }
13259 #endif
13260
13261     if (! (cmailMailedMove || RegisterMove())) return;
13262
13263     if (   cmailMailedMove
13264         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13265       snprintf(string, MSG_SIZ, partCommandString,
13266                appData.debugMode ? " -v" : "", appData.cmailGameName);
13267         commandOutput = popen(string, "r");
13268
13269         if (commandOutput == NULL) {
13270             DisplayError(_("Failed to invoke cmail"), 0);
13271         } else {
13272             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13273                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13274             }
13275             if (nBuffers > 1) {
13276                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13277                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13278                 nBytes = MSG_SIZ - 1;
13279             } else {
13280                 (void) memcpy(msg, buffer, nBytes);
13281             }
13282             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13283
13284             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13285                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13286
13287                 archived = TRUE;
13288                 for (i = 0; i < nCmailGames; i ++) {
13289                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13290                         archived = FALSE;
13291                     }
13292                 }
13293                 if (   archived
13294                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13295                         != NULL)) {
13296                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13297                            arcDir,
13298                            appData.cmailGameName,
13299                            gameInfo.date);
13300                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13301                     cmailMsgLoaded = FALSE;
13302                 }
13303             }
13304
13305             DisplayInformation(msg);
13306             pclose(commandOutput);
13307         }
13308     } else {
13309         if ((*cmailMsg) != '\0') {
13310             DisplayInformation(cmailMsg);
13311         }
13312     }
13313
13314     return;
13315 #endif /* !WIN32 */
13316 }
13317
13318 char *
13319 CmailMsg ()
13320 {
13321 #if WIN32
13322     return NULL;
13323 #else
13324     int  prependComma = 0;
13325     char number[5];
13326     char string[MSG_SIZ];       /* Space for game-list */
13327     int  i;
13328
13329     if (!cmailMsgLoaded) return "";
13330
13331     if (cmailMailedMove) {
13332       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13333     } else {
13334         /* Create a list of games left */
13335       snprintf(string, MSG_SIZ, "[");
13336         for (i = 0; i < nCmailGames; i ++) {
13337             if (! (   cmailMoveRegistered[i]
13338                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13339                 if (prependComma) {
13340                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13341                 } else {
13342                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13343                     prependComma = 1;
13344                 }
13345
13346                 strcat(string, number);
13347             }
13348         }
13349         strcat(string, "]");
13350
13351         if (nCmailMovesRegistered + nCmailResults == 0) {
13352             switch (nCmailGames) {
13353               case 1:
13354                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13355                 break;
13356
13357               case 2:
13358                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13359                 break;
13360
13361               default:
13362                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13363                          nCmailGames);
13364                 break;
13365             }
13366         } else {
13367             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13368               case 1:
13369                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13370                          string);
13371                 break;
13372
13373               case 0:
13374                 if (nCmailResults == nCmailGames) {
13375                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13376                 } else {
13377                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13378                 }
13379                 break;
13380
13381               default:
13382                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13383                          string);
13384             }
13385         }
13386     }
13387     return cmailMsg;
13388 #endif /* WIN32 */
13389 }
13390
13391 void
13392 ResetGameEvent ()
13393 {
13394     if (gameMode == Training)
13395       SetTrainingModeOff();
13396
13397     Reset(TRUE, TRUE);
13398     cmailMsgLoaded = FALSE;
13399     if (appData.icsActive) {
13400       SendToICS(ics_prefix);
13401       SendToICS("refresh\n");
13402     }
13403 }
13404
13405 void
13406 ExitEvent (int status)
13407 {
13408     exiting++;
13409     if (exiting > 2) {
13410       /* Give up on clean exit */
13411       exit(status);
13412     }
13413     if (exiting > 1) {
13414       /* Keep trying for clean exit */
13415       return;
13416     }
13417
13418     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13419
13420     if (telnetISR != NULL) {
13421       RemoveInputSource(telnetISR);
13422     }
13423     if (icsPR != NoProc) {
13424       DestroyChildProcess(icsPR, TRUE);
13425     }
13426
13427     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13428     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13429
13430     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13431     /* make sure this other one finishes before killing it!                  */
13432     if(endingGame) { int count = 0;
13433         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13434         while(endingGame && count++ < 10) DoSleep(1);
13435         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13436     }
13437
13438     /* Kill off chess programs */
13439     if (first.pr != NoProc) {
13440         ExitAnalyzeMode();
13441
13442         DoSleep( appData.delayBeforeQuit );
13443         SendToProgram("quit\n", &first);
13444         DoSleep( appData.delayAfterQuit );
13445         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13446     }
13447     if (second.pr != NoProc) {
13448         DoSleep( appData.delayBeforeQuit );
13449         SendToProgram("quit\n", &second);
13450         DoSleep( appData.delayAfterQuit );
13451         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13452     }
13453     if (first.isr != NULL) {
13454         RemoveInputSource(first.isr);
13455     }
13456     if (second.isr != NULL) {
13457         RemoveInputSource(second.isr);
13458     }
13459
13460     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13461     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13462
13463     ShutDownFrontEnd();
13464     exit(status);
13465 }
13466
13467 void
13468 PauseEngine (ChessProgramState *cps)
13469 {
13470     SendToProgram("pause\n", cps);
13471     cps->pause = 2;
13472 }
13473
13474 void
13475 UnPauseEngine (ChessProgramState *cps)
13476 {
13477     SendToProgram("resume\n", cps);
13478     cps->pause = 1;
13479 }
13480
13481 void
13482 PauseEvent ()
13483 {
13484     if (appData.debugMode)
13485         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13486     if (pausing) {
13487         pausing = FALSE;
13488         ModeHighlight();
13489         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13490             StartClocks();
13491             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13492                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13493                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13494             }
13495             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13496             HandleMachineMove(stashedInputMove, stalledEngine);
13497             stalledEngine = NULL;
13498             return;
13499         }
13500         if (gameMode == MachinePlaysWhite ||
13501             gameMode == TwoMachinesPlay   ||
13502             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13503             if(first.pause)  UnPauseEngine(&first);
13504             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13505             if(second.pause) UnPauseEngine(&second);
13506             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13507             StartClocks();
13508         } else {
13509             DisplayBothClocks();
13510         }
13511         if (gameMode == PlayFromGameFile) {
13512             if (appData.timeDelay >= 0)
13513                 AutoPlayGameLoop();
13514         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13515             Reset(FALSE, TRUE);
13516             SendToICS(ics_prefix);
13517             SendToICS("refresh\n");
13518         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13519             ForwardInner(forwardMostMove);
13520         }
13521         pauseExamInvalid = FALSE;
13522     } else {
13523         switch (gameMode) {
13524           default:
13525             return;
13526           case IcsExamining:
13527             pauseExamForwardMostMove = forwardMostMove;
13528             pauseExamInvalid = FALSE;
13529             /* fall through */
13530           case IcsObserving:
13531           case IcsPlayingWhite:
13532           case IcsPlayingBlack:
13533             pausing = TRUE;
13534             ModeHighlight();
13535             return;
13536           case PlayFromGameFile:
13537             (void) StopLoadGameTimer();
13538             pausing = TRUE;
13539             ModeHighlight();
13540             break;
13541           case BeginningOfGame:
13542             if (appData.icsActive) return;
13543             /* else fall through */
13544           case MachinePlaysWhite:
13545           case MachinePlaysBlack:
13546           case TwoMachinesPlay:
13547             if (forwardMostMove == 0)
13548               return;           /* don't pause if no one has moved */
13549             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13550                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13551                 if(onMove->pause) {           // thinking engine can be paused
13552                     PauseEngine(onMove);      // do it
13553                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13554                         PauseEngine(onMove->other);
13555                     else
13556                         SendToProgram("easy\n", onMove->other);
13557                     StopClocks();
13558                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13559             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13560                 if(first.pause) {
13561                     PauseEngine(&first);
13562                     StopClocks();
13563                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13564             } else { // human on move, pause pondering by either method
13565                 if(first.pause)
13566                     PauseEngine(&first);
13567                 else if(appData.ponderNextMove)
13568                     SendToProgram("easy\n", &first);
13569                 StopClocks();
13570             }
13571             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13572           case AnalyzeMode:
13573             pausing = TRUE;
13574             ModeHighlight();
13575             break;
13576         }
13577     }
13578 }
13579
13580 void
13581 EditCommentEvent ()
13582 {
13583     char title[MSG_SIZ];
13584
13585     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13586       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13587     } else {
13588       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13589                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13590                parseList[currentMove - 1]);
13591     }
13592
13593     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13594 }
13595
13596
13597 void
13598 EditTagsEvent ()
13599 {
13600     char *tags = PGNTags(&gameInfo);
13601     bookUp = FALSE;
13602     EditTagsPopUp(tags, NULL);
13603     free(tags);
13604 }
13605
13606 void
13607 ToggleSecond ()
13608 {
13609   if(second.analyzing) {
13610     SendToProgram("exit\n", &second);
13611     second.analyzing = FALSE;
13612   } else {
13613     if (second.pr == NoProc) StartChessProgram(&second);
13614     InitChessProgram(&second, FALSE);
13615     FeedMovesToProgram(&second, currentMove);
13616
13617     SendToProgram("analyze\n", &second);
13618     second.analyzing = TRUE;
13619   }
13620 }
13621
13622 /* Toggle ShowThinking */
13623 void
13624 ToggleShowThinking()
13625 {
13626   appData.showThinking = !appData.showThinking;
13627   ShowThinkingEvent();
13628 }
13629
13630 int
13631 AnalyzeModeEvent ()
13632 {
13633     char buf[MSG_SIZ];
13634
13635     if (!first.analysisSupport) {
13636       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13637       DisplayError(buf, 0);
13638       return 0;
13639     }
13640     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13641     if (appData.icsActive) {
13642         if (gameMode != IcsObserving) {
13643           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13644             DisplayError(buf, 0);
13645             /* secure check */
13646             if (appData.icsEngineAnalyze) {
13647                 if (appData.debugMode)
13648                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13649                 ExitAnalyzeMode();
13650                 ModeHighlight();
13651             }
13652             return 0;
13653         }
13654         /* if enable, user wants to disable icsEngineAnalyze */
13655         if (appData.icsEngineAnalyze) {
13656                 ExitAnalyzeMode();
13657                 ModeHighlight();
13658                 return 0;
13659         }
13660         appData.icsEngineAnalyze = TRUE;
13661         if (appData.debugMode)
13662             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13663     }
13664
13665     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13666     if (appData.noChessProgram || gameMode == AnalyzeMode)
13667       return 0;
13668
13669     if (gameMode != AnalyzeFile) {
13670         if (!appData.icsEngineAnalyze) {
13671                EditGameEvent();
13672                if (gameMode != EditGame) return 0;
13673         }
13674         if (!appData.showThinking) ToggleShowThinking();
13675         ResurrectChessProgram();
13676         SendToProgram("analyze\n", &first);
13677         first.analyzing = TRUE;
13678         /*first.maybeThinking = TRUE;*/
13679         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13680         EngineOutputPopUp();
13681     }
13682     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13683     pausing = FALSE;
13684     ModeHighlight();
13685     SetGameInfo();
13686
13687     StartAnalysisClock();
13688     GetTimeMark(&lastNodeCountTime);
13689     lastNodeCount = 0;
13690     return 1;
13691 }
13692
13693 void
13694 AnalyzeFileEvent ()
13695 {
13696     if (appData.noChessProgram || gameMode == AnalyzeFile)
13697       return;
13698
13699     if (!first.analysisSupport) {
13700       char buf[MSG_SIZ];
13701       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13702       DisplayError(buf, 0);
13703       return;
13704     }
13705
13706     if (gameMode != AnalyzeMode) {
13707         keepInfo = 1; // mere annotating should not alter PGN tags
13708         EditGameEvent();
13709         keepInfo = 0;
13710         if (gameMode != EditGame) return;
13711         if (!appData.showThinking) ToggleShowThinking();
13712         ResurrectChessProgram();
13713         SendToProgram("analyze\n", &first);
13714         first.analyzing = TRUE;
13715         /*first.maybeThinking = TRUE;*/
13716         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13717         EngineOutputPopUp();
13718     }
13719     gameMode = AnalyzeFile;
13720     pausing = FALSE;
13721     ModeHighlight();
13722
13723     StartAnalysisClock();
13724     GetTimeMark(&lastNodeCountTime);
13725     lastNodeCount = 0;
13726     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13727     AnalysisPeriodicEvent(1);
13728 }
13729
13730 void
13731 MachineWhiteEvent ()
13732 {
13733     char buf[MSG_SIZ];
13734     char *bookHit = NULL;
13735
13736     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13737       return;
13738
13739
13740     if (gameMode == PlayFromGameFile ||
13741         gameMode == TwoMachinesPlay  ||
13742         gameMode == Training         ||
13743         gameMode == AnalyzeMode      ||
13744         gameMode == EndOfGame)
13745         EditGameEvent();
13746
13747     if (gameMode == EditPosition)
13748         EditPositionDone(TRUE);
13749
13750     if (!WhiteOnMove(currentMove)) {
13751         DisplayError(_("It is not White's turn"), 0);
13752         return;
13753     }
13754
13755     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13756       ExitAnalyzeMode();
13757
13758     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13759         gameMode == AnalyzeFile)
13760         TruncateGame();
13761
13762     ResurrectChessProgram();    /* in case it isn't running */
13763     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13764         gameMode = MachinePlaysWhite;
13765         ResetClocks();
13766     } else
13767     gameMode = MachinePlaysWhite;
13768     pausing = FALSE;
13769     ModeHighlight();
13770     SetGameInfo();
13771     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13772     DisplayTitle(buf);
13773     if (first.sendName) {
13774       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13775       SendToProgram(buf, &first);
13776     }
13777     if (first.sendTime) {
13778       if (first.useColors) {
13779         SendToProgram("black\n", &first); /*gnu kludge*/
13780       }
13781       SendTimeRemaining(&first, TRUE);
13782     }
13783     if (first.useColors) {
13784       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13785     }
13786     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13787     SetMachineThinkingEnables();
13788     first.maybeThinking = TRUE;
13789     StartClocks();
13790     firstMove = FALSE;
13791
13792     if (appData.autoFlipView && !flipView) {
13793       flipView = !flipView;
13794       DrawPosition(FALSE, NULL);
13795       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13796     }
13797
13798     if(bookHit) { // [HGM] book: simulate book reply
13799         static char bookMove[MSG_SIZ]; // a bit generous?
13800
13801         programStats.nodes = programStats.depth = programStats.time =
13802         programStats.score = programStats.got_only_move = 0;
13803         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13804
13805         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13806         strcat(bookMove, bookHit);
13807         HandleMachineMove(bookMove, &first);
13808     }
13809 }
13810
13811 void
13812 MachineBlackEvent ()
13813 {
13814   char buf[MSG_SIZ];
13815   char *bookHit = NULL;
13816
13817     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13818         return;
13819
13820
13821     if (gameMode == PlayFromGameFile ||
13822         gameMode == TwoMachinesPlay  ||
13823         gameMode == Training         ||
13824         gameMode == AnalyzeMode      ||
13825         gameMode == EndOfGame)
13826         EditGameEvent();
13827
13828     if (gameMode == EditPosition)
13829         EditPositionDone(TRUE);
13830
13831     if (WhiteOnMove(currentMove)) {
13832         DisplayError(_("It is not Black's turn"), 0);
13833         return;
13834     }
13835
13836     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13837       ExitAnalyzeMode();
13838
13839     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13840         gameMode == AnalyzeFile)
13841         TruncateGame();
13842
13843     ResurrectChessProgram();    /* in case it isn't running */
13844     gameMode = MachinePlaysBlack;
13845     pausing = FALSE;
13846     ModeHighlight();
13847     SetGameInfo();
13848     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13849     DisplayTitle(buf);
13850     if (first.sendName) {
13851       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13852       SendToProgram(buf, &first);
13853     }
13854     if (first.sendTime) {
13855       if (first.useColors) {
13856         SendToProgram("white\n", &first); /*gnu kludge*/
13857       }
13858       SendTimeRemaining(&first, FALSE);
13859     }
13860     if (first.useColors) {
13861       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13862     }
13863     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13864     SetMachineThinkingEnables();
13865     first.maybeThinking = TRUE;
13866     StartClocks();
13867
13868     if (appData.autoFlipView && flipView) {
13869       flipView = !flipView;
13870       DrawPosition(FALSE, NULL);
13871       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13872     }
13873     if(bookHit) { // [HGM] book: simulate book reply
13874         static char bookMove[MSG_SIZ]; // a bit generous?
13875
13876         programStats.nodes = programStats.depth = programStats.time =
13877         programStats.score = programStats.got_only_move = 0;
13878         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13879
13880         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13881         strcat(bookMove, bookHit);
13882         HandleMachineMove(bookMove, &first);
13883     }
13884 }
13885
13886
13887 void
13888 DisplayTwoMachinesTitle ()
13889 {
13890     char buf[MSG_SIZ];
13891     if (appData.matchGames > 0) {
13892         if(appData.tourneyFile[0]) {
13893           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13894                    gameInfo.white, _("vs."), gameInfo.black,
13895                    nextGame+1, appData.matchGames+1,
13896                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13897         } else
13898         if (first.twoMachinesColor[0] == 'w') {
13899           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13900                    gameInfo.white, _("vs."),  gameInfo.black,
13901                    first.matchWins, second.matchWins,
13902                    matchGame - 1 - (first.matchWins + second.matchWins));
13903         } else {
13904           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13905                    gameInfo.white, _("vs."), gameInfo.black,
13906                    second.matchWins, first.matchWins,
13907                    matchGame - 1 - (first.matchWins + second.matchWins));
13908         }
13909     } else {
13910       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13911     }
13912     DisplayTitle(buf);
13913 }
13914
13915 void
13916 SettingsMenuIfReady ()
13917 {
13918   if (second.lastPing != second.lastPong) {
13919     DisplayMessage("", _("Waiting for second chess program"));
13920     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13921     return;
13922   }
13923   ThawUI();
13924   DisplayMessage("", "");
13925   SettingsPopUp(&second);
13926 }
13927
13928 int
13929 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13930 {
13931     char buf[MSG_SIZ];
13932     if (cps->pr == NoProc) {
13933         StartChessProgram(cps);
13934         if (cps->protocolVersion == 1) {
13935           retry();
13936           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13937         } else {
13938           /* kludge: allow timeout for initial "feature" command */
13939           if(retry != TwoMachinesEventIfReady) FreezeUI();
13940           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13941           DisplayMessage("", buf);
13942           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13943         }
13944         return 1;
13945     }
13946     return 0;
13947 }
13948
13949 void
13950 TwoMachinesEvent P((void))
13951 {
13952     int i;
13953     char buf[MSG_SIZ];
13954     ChessProgramState *onmove;
13955     char *bookHit = NULL;
13956     static int stalling = 0;
13957     TimeMark now;
13958     long wait;
13959
13960     if (appData.noChessProgram) return;
13961
13962     switch (gameMode) {
13963       case TwoMachinesPlay:
13964         return;
13965       case MachinePlaysWhite:
13966       case MachinePlaysBlack:
13967         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13968             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13969             return;
13970         }
13971         /* fall through */
13972       case BeginningOfGame:
13973       case PlayFromGameFile:
13974       case EndOfGame:
13975         EditGameEvent();
13976         if (gameMode != EditGame) return;
13977         break;
13978       case EditPosition:
13979         EditPositionDone(TRUE);
13980         break;
13981       case AnalyzeMode:
13982       case AnalyzeFile:
13983         ExitAnalyzeMode();
13984         break;
13985       case EditGame:
13986       default:
13987         break;
13988     }
13989
13990 //    forwardMostMove = currentMove;
13991     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13992     startingEngine = TRUE;
13993
13994     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13995
13996     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
13997     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13998       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13999       return;
14000     }
14001     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14002
14003     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14004         startingEngine = FALSE;
14005         DisplayError("second engine does not play this", 0);
14006         return;
14007     }
14008
14009     if(!stalling) {
14010       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14011       SendToProgram("force\n", &second);
14012       stalling = 1;
14013       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14014       return;
14015     }
14016     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14017     if(appData.matchPause>10000 || appData.matchPause<10)
14018                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14019     wait = SubtractTimeMarks(&now, &pauseStart);
14020     if(wait < appData.matchPause) {
14021         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14022         return;
14023     }
14024     // we are now committed to starting the game
14025     stalling = 0;
14026     DisplayMessage("", "");
14027     if (startedFromSetupPosition) {
14028         SendBoard(&second, backwardMostMove);
14029     if (appData.debugMode) {
14030         fprintf(debugFP, "Two Machines\n");
14031     }
14032     }
14033     for (i = backwardMostMove; i < forwardMostMove; i++) {
14034         SendMoveToProgram(i, &second);
14035     }
14036
14037     gameMode = TwoMachinesPlay;
14038     pausing = startingEngine = FALSE;
14039     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14040     SetGameInfo();
14041     DisplayTwoMachinesTitle();
14042     firstMove = TRUE;
14043     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14044         onmove = &first;
14045     } else {
14046         onmove = &second;
14047     }
14048     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14049     SendToProgram(first.computerString, &first);
14050     if (first.sendName) {
14051       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14052       SendToProgram(buf, &first);
14053     }
14054     SendToProgram(second.computerString, &second);
14055     if (second.sendName) {
14056       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14057       SendToProgram(buf, &second);
14058     }
14059
14060     ResetClocks();
14061     if (!first.sendTime || !second.sendTime) {
14062         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14063         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14064     }
14065     if (onmove->sendTime) {
14066       if (onmove->useColors) {
14067         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14068       }
14069       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14070     }
14071     if (onmove->useColors) {
14072       SendToProgram(onmove->twoMachinesColor, onmove);
14073     }
14074     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14075 //    SendToProgram("go\n", onmove);
14076     onmove->maybeThinking = TRUE;
14077     SetMachineThinkingEnables();
14078
14079     StartClocks();
14080
14081     if(bookHit) { // [HGM] book: simulate book reply
14082         static char bookMove[MSG_SIZ]; // a bit generous?
14083
14084         programStats.nodes = programStats.depth = programStats.time =
14085         programStats.score = programStats.got_only_move = 0;
14086         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14087
14088         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14089         strcat(bookMove, bookHit);
14090         savedMessage = bookMove; // args for deferred call
14091         savedState = onmove;
14092         ScheduleDelayedEvent(DeferredBookMove, 1);
14093     }
14094 }
14095
14096 void
14097 TrainingEvent ()
14098 {
14099     if (gameMode == Training) {
14100       SetTrainingModeOff();
14101       gameMode = PlayFromGameFile;
14102       DisplayMessage("", _("Training mode off"));
14103     } else {
14104       gameMode = Training;
14105       animateTraining = appData.animate;
14106
14107       /* make sure we are not already at the end of the game */
14108       if (currentMove < forwardMostMove) {
14109         SetTrainingModeOn();
14110         DisplayMessage("", _("Training mode on"));
14111       } else {
14112         gameMode = PlayFromGameFile;
14113         DisplayError(_("Already at end of game"), 0);
14114       }
14115     }
14116     ModeHighlight();
14117 }
14118
14119 void
14120 IcsClientEvent ()
14121 {
14122     if (!appData.icsActive) return;
14123     switch (gameMode) {
14124       case IcsPlayingWhite:
14125       case IcsPlayingBlack:
14126       case IcsObserving:
14127       case IcsIdle:
14128       case BeginningOfGame:
14129       case IcsExamining:
14130         return;
14131
14132       case EditGame:
14133         break;
14134
14135       case EditPosition:
14136         EditPositionDone(TRUE);
14137         break;
14138
14139       case AnalyzeMode:
14140       case AnalyzeFile:
14141         ExitAnalyzeMode();
14142         break;
14143
14144       default:
14145         EditGameEvent();
14146         break;
14147     }
14148
14149     gameMode = IcsIdle;
14150     ModeHighlight();
14151     return;
14152 }
14153
14154 void
14155 EditGameEvent ()
14156 {
14157     int i;
14158
14159     switch (gameMode) {
14160       case Training:
14161         SetTrainingModeOff();
14162         break;
14163       case MachinePlaysWhite:
14164       case MachinePlaysBlack:
14165       case BeginningOfGame:
14166         SendToProgram("force\n", &first);
14167         SetUserThinkingEnables();
14168         break;
14169       case PlayFromGameFile:
14170         (void) StopLoadGameTimer();
14171         if (gameFileFP != NULL) {
14172             gameFileFP = NULL;
14173         }
14174         break;
14175       case EditPosition:
14176         EditPositionDone(TRUE);
14177         break;
14178       case AnalyzeMode:
14179       case AnalyzeFile:
14180         ExitAnalyzeMode();
14181         SendToProgram("force\n", &first);
14182         break;
14183       case TwoMachinesPlay:
14184         GameEnds(EndOfFile, NULL, GE_PLAYER);
14185         ResurrectChessProgram();
14186         SetUserThinkingEnables();
14187         break;
14188       case EndOfGame:
14189         ResurrectChessProgram();
14190         break;
14191       case IcsPlayingBlack:
14192       case IcsPlayingWhite:
14193         DisplayError(_("Warning: You are still playing a game"), 0);
14194         break;
14195       case IcsObserving:
14196         DisplayError(_("Warning: You are still observing a game"), 0);
14197         break;
14198       case IcsExamining:
14199         DisplayError(_("Warning: You are still examining a game"), 0);
14200         break;
14201       case IcsIdle:
14202         break;
14203       case EditGame:
14204       default:
14205         return;
14206     }
14207
14208     pausing = FALSE;
14209     StopClocks();
14210     first.offeredDraw = second.offeredDraw = 0;
14211
14212     if (gameMode == PlayFromGameFile) {
14213         whiteTimeRemaining = timeRemaining[0][currentMove];
14214         blackTimeRemaining = timeRemaining[1][currentMove];
14215         DisplayTitle("");
14216     }
14217
14218     if (gameMode == MachinePlaysWhite ||
14219         gameMode == MachinePlaysBlack ||
14220         gameMode == TwoMachinesPlay ||
14221         gameMode == EndOfGame) {
14222         i = forwardMostMove;
14223         while (i > currentMove) {
14224             SendToProgram("undo\n", &first);
14225             i--;
14226         }
14227         if(!adjustedClock) {
14228         whiteTimeRemaining = timeRemaining[0][currentMove];
14229         blackTimeRemaining = timeRemaining[1][currentMove];
14230         DisplayBothClocks();
14231         }
14232         if (whiteFlag || blackFlag) {
14233             whiteFlag = blackFlag = 0;
14234         }
14235         DisplayTitle("");
14236     }
14237
14238     gameMode = EditGame;
14239     ModeHighlight();
14240     SetGameInfo();
14241 }
14242
14243
14244 void
14245 EditPositionEvent ()
14246 {
14247     if (gameMode == EditPosition) {
14248         EditGameEvent();
14249         return;
14250     }
14251
14252     EditGameEvent();
14253     if (gameMode != EditGame) return;
14254
14255     gameMode = EditPosition;
14256     ModeHighlight();
14257     SetGameInfo();
14258     if (currentMove > 0)
14259       CopyBoard(boards[0], boards[currentMove]);
14260
14261     blackPlaysFirst = !WhiteOnMove(currentMove);
14262     ResetClocks();
14263     currentMove = forwardMostMove = backwardMostMove = 0;
14264     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14265     DisplayMove(-1);
14266     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14267 }
14268
14269 void
14270 ExitAnalyzeMode ()
14271 {
14272     /* [DM] icsEngineAnalyze - possible call from other functions */
14273     if (appData.icsEngineAnalyze) {
14274         appData.icsEngineAnalyze = FALSE;
14275
14276         DisplayMessage("",_("Close ICS engine analyze..."));
14277     }
14278     if (first.analysisSupport && first.analyzing) {
14279       SendToBoth("exit\n");
14280       first.analyzing = second.analyzing = FALSE;
14281     }
14282     thinkOutput[0] = NULLCHAR;
14283 }
14284
14285 void
14286 EditPositionDone (Boolean fakeRights)
14287 {
14288     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14289
14290     startedFromSetupPosition = TRUE;
14291     InitChessProgram(&first, FALSE);
14292     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14293       boards[0][EP_STATUS] = EP_NONE;
14294       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14295       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14296         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14297         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14298       } else boards[0][CASTLING][2] = NoRights;
14299       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14300         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14301         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14302       } else boards[0][CASTLING][5] = NoRights;
14303       if(gameInfo.variant == VariantSChess) {
14304         int i;
14305         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14306           boards[0][VIRGIN][i] = 0;
14307           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14308           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14309         }
14310       }
14311     }
14312     SendToProgram("force\n", &first);
14313     if (blackPlaysFirst) {
14314         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14315         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14316         currentMove = forwardMostMove = backwardMostMove = 1;
14317         CopyBoard(boards[1], boards[0]);
14318     } else {
14319         currentMove = forwardMostMove = backwardMostMove = 0;
14320     }
14321     SendBoard(&first, forwardMostMove);
14322     if (appData.debugMode) {
14323         fprintf(debugFP, "EditPosDone\n");
14324     }
14325     DisplayTitle("");
14326     DisplayMessage("", "");
14327     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14328     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14329     gameMode = EditGame;
14330     ModeHighlight();
14331     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14332     ClearHighlights(); /* [AS] */
14333 }
14334
14335 /* Pause for `ms' milliseconds */
14336 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14337 void
14338 TimeDelay (long ms)
14339 {
14340     TimeMark m1, m2;
14341
14342     GetTimeMark(&m1);
14343     do {
14344         GetTimeMark(&m2);
14345     } while (SubtractTimeMarks(&m2, &m1) < ms);
14346 }
14347
14348 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14349 void
14350 SendMultiLineToICS (char *buf)
14351 {
14352     char temp[MSG_SIZ+1], *p;
14353     int len;
14354
14355     len = strlen(buf);
14356     if (len > MSG_SIZ)
14357       len = MSG_SIZ;
14358
14359     strncpy(temp, buf, len);
14360     temp[len] = 0;
14361
14362     p = temp;
14363     while (*p) {
14364         if (*p == '\n' || *p == '\r')
14365           *p = ' ';
14366         ++p;
14367     }
14368
14369     strcat(temp, "\n");
14370     SendToICS(temp);
14371     SendToPlayer(temp, strlen(temp));
14372 }
14373
14374 void
14375 SetWhiteToPlayEvent ()
14376 {
14377     if (gameMode == EditPosition) {
14378         blackPlaysFirst = FALSE;
14379         DisplayBothClocks();    /* works because currentMove is 0 */
14380     } else if (gameMode == IcsExamining) {
14381         SendToICS(ics_prefix);
14382         SendToICS("tomove white\n");
14383     }
14384 }
14385
14386 void
14387 SetBlackToPlayEvent ()
14388 {
14389     if (gameMode == EditPosition) {
14390         blackPlaysFirst = TRUE;
14391         currentMove = 1;        /* kludge */
14392         DisplayBothClocks();
14393         currentMove = 0;
14394     } else if (gameMode == IcsExamining) {
14395         SendToICS(ics_prefix);
14396         SendToICS("tomove black\n");
14397     }
14398 }
14399
14400 void
14401 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14402 {
14403     char buf[MSG_SIZ];
14404     ChessSquare piece = boards[0][y][x];
14405
14406     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14407
14408     switch (selection) {
14409       case ClearBoard:
14410         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14411             SendToICS(ics_prefix);
14412             SendToICS("bsetup clear\n");
14413         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14414             SendToICS(ics_prefix);
14415             SendToICS("clearboard\n");
14416         } else {
14417             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14418                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14419                 for (y = 0; y < BOARD_HEIGHT; y++) {
14420                     if (gameMode == IcsExamining) {
14421                         if (boards[currentMove][y][x] != EmptySquare) {
14422                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14423                                     AAA + x, ONE + y);
14424                             SendToICS(buf);
14425                         }
14426                     } else {
14427                         boards[0][y][x] = p;
14428                     }
14429                 }
14430             }
14431         }
14432         if (gameMode == EditPosition) {
14433             DrawPosition(FALSE, boards[0]);
14434         }
14435         break;
14436
14437       case WhitePlay:
14438         SetWhiteToPlayEvent();
14439         break;
14440
14441       case BlackPlay:
14442         SetBlackToPlayEvent();
14443         break;
14444
14445       case EmptySquare:
14446         if (gameMode == IcsExamining) {
14447             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14448             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14449             SendToICS(buf);
14450         } else {
14451             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14452                 if(x == BOARD_LEFT-2) {
14453                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14454                     boards[0][y][1] = 0;
14455                 } else
14456                 if(x == BOARD_RGHT+1) {
14457                     if(y >= gameInfo.holdingsSize) break;
14458                     boards[0][y][BOARD_WIDTH-2] = 0;
14459                 } else break;
14460             }
14461             boards[0][y][x] = EmptySquare;
14462             DrawPosition(FALSE, boards[0]);
14463         }
14464         break;
14465
14466       case PromotePiece:
14467         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14468            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14469             selection = (ChessSquare) (PROMOTED piece);
14470         } else if(piece == EmptySquare) selection = WhiteSilver;
14471         else selection = (ChessSquare)((int)piece - 1);
14472         goto defaultlabel;
14473
14474       case DemotePiece:
14475         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14476            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14477             selection = (ChessSquare) (DEMOTED piece);
14478         } else if(piece == EmptySquare) selection = BlackSilver;
14479         else selection = (ChessSquare)((int)piece + 1);
14480         goto defaultlabel;
14481
14482       case WhiteQueen:
14483       case BlackQueen:
14484         if(gameInfo.variant == VariantShatranj ||
14485            gameInfo.variant == VariantXiangqi  ||
14486            gameInfo.variant == VariantCourier  ||
14487            gameInfo.variant == VariantMakruk     )
14488             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14489         goto defaultlabel;
14490
14491       case WhiteKing:
14492       case BlackKing:
14493         if(gameInfo.variant == VariantXiangqi)
14494             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14495         if(gameInfo.variant == VariantKnightmate)
14496             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14497       default:
14498         defaultlabel:
14499         if (gameMode == IcsExamining) {
14500             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14501             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14502                      PieceToChar(selection), AAA + x, ONE + y);
14503             SendToICS(buf);
14504         } else {
14505             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14506                 int n;
14507                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14508                     n = PieceToNumber(selection - BlackPawn);
14509                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14510                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14511                     boards[0][BOARD_HEIGHT-1-n][1]++;
14512                 } else
14513                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14514                     n = PieceToNumber(selection);
14515                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14516                     boards[0][n][BOARD_WIDTH-1] = selection;
14517                     boards[0][n][BOARD_WIDTH-2]++;
14518                 }
14519             } else
14520             boards[0][y][x] = selection;
14521             DrawPosition(TRUE, boards[0]);
14522             ClearHighlights();
14523             fromX = fromY = -1;
14524         }
14525         break;
14526     }
14527 }
14528
14529
14530 void
14531 DropMenuEvent (ChessSquare selection, int x, int y)
14532 {
14533     ChessMove moveType;
14534
14535     switch (gameMode) {
14536       case IcsPlayingWhite:
14537       case MachinePlaysBlack:
14538         if (!WhiteOnMove(currentMove)) {
14539             DisplayMoveError(_("It is Black's turn"));
14540             return;
14541         }
14542         moveType = WhiteDrop;
14543         break;
14544       case IcsPlayingBlack:
14545       case MachinePlaysWhite:
14546         if (WhiteOnMove(currentMove)) {
14547             DisplayMoveError(_("It is White's turn"));
14548             return;
14549         }
14550         moveType = BlackDrop;
14551         break;
14552       case EditGame:
14553         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14554         break;
14555       default:
14556         return;
14557     }
14558
14559     if (moveType == BlackDrop && selection < BlackPawn) {
14560       selection = (ChessSquare) ((int) selection
14561                                  + (int) BlackPawn - (int) WhitePawn);
14562     }
14563     if (boards[currentMove][y][x] != EmptySquare) {
14564         DisplayMoveError(_("That square is occupied"));
14565         return;
14566     }
14567
14568     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14569 }
14570
14571 void
14572 AcceptEvent ()
14573 {
14574     /* Accept a pending offer of any kind from opponent */
14575
14576     if (appData.icsActive) {
14577         SendToICS(ics_prefix);
14578         SendToICS("accept\n");
14579     } else if (cmailMsgLoaded) {
14580         if (currentMove == cmailOldMove &&
14581             commentList[cmailOldMove] != NULL &&
14582             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14583                    "Black offers a draw" : "White offers a draw")) {
14584             TruncateGame();
14585             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14586             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14587         } else {
14588             DisplayError(_("There is no pending offer on this move"), 0);
14589             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14590         }
14591     } else {
14592         /* Not used for offers from chess program */
14593     }
14594 }
14595
14596 void
14597 DeclineEvent ()
14598 {
14599     /* Decline a pending offer of any kind from opponent */
14600
14601     if (appData.icsActive) {
14602         SendToICS(ics_prefix);
14603         SendToICS("decline\n");
14604     } else if (cmailMsgLoaded) {
14605         if (currentMove == cmailOldMove &&
14606             commentList[cmailOldMove] != NULL &&
14607             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14608                    "Black offers a draw" : "White offers a draw")) {
14609 #ifdef NOTDEF
14610             AppendComment(cmailOldMove, "Draw declined", TRUE);
14611             DisplayComment(cmailOldMove - 1, "Draw declined");
14612 #endif /*NOTDEF*/
14613         } else {
14614             DisplayError(_("There is no pending offer on this move"), 0);
14615         }
14616     } else {
14617         /* Not used for offers from chess program */
14618     }
14619 }
14620
14621 void
14622 RematchEvent ()
14623 {
14624     /* Issue ICS rematch command */
14625     if (appData.icsActive) {
14626         SendToICS(ics_prefix);
14627         SendToICS("rematch\n");
14628     }
14629 }
14630
14631 void
14632 CallFlagEvent ()
14633 {
14634     /* Call your opponent's flag (claim a win on time) */
14635     if (appData.icsActive) {
14636         SendToICS(ics_prefix);
14637         SendToICS("flag\n");
14638     } else {
14639         switch (gameMode) {
14640           default:
14641             return;
14642           case MachinePlaysWhite:
14643             if (whiteFlag) {
14644                 if (blackFlag)
14645                   GameEnds(GameIsDrawn, "Both players ran out of time",
14646                            GE_PLAYER);
14647                 else
14648                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14649             } else {
14650                 DisplayError(_("Your opponent is not out of time"), 0);
14651             }
14652             break;
14653           case MachinePlaysBlack:
14654             if (blackFlag) {
14655                 if (whiteFlag)
14656                   GameEnds(GameIsDrawn, "Both players ran out of time",
14657                            GE_PLAYER);
14658                 else
14659                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14660             } else {
14661                 DisplayError(_("Your opponent is not out of time"), 0);
14662             }
14663             break;
14664         }
14665     }
14666 }
14667
14668 void
14669 ClockClick (int which)
14670 {       // [HGM] code moved to back-end from winboard.c
14671         if(which) { // black clock
14672           if (gameMode == EditPosition || gameMode == IcsExamining) {
14673             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14674             SetBlackToPlayEvent();
14675           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14676           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14677           } else if (shiftKey) {
14678             AdjustClock(which, -1);
14679           } else if (gameMode == IcsPlayingWhite ||
14680                      gameMode == MachinePlaysBlack) {
14681             CallFlagEvent();
14682           }
14683         } else { // white clock
14684           if (gameMode == EditPosition || gameMode == IcsExamining) {
14685             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14686             SetWhiteToPlayEvent();
14687           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14688           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14689           } else if (shiftKey) {
14690             AdjustClock(which, -1);
14691           } else if (gameMode == IcsPlayingBlack ||
14692                    gameMode == MachinePlaysWhite) {
14693             CallFlagEvent();
14694           }
14695         }
14696 }
14697
14698 void
14699 DrawEvent ()
14700 {
14701     /* Offer draw or accept pending draw offer from opponent */
14702
14703     if (appData.icsActive) {
14704         /* Note: tournament rules require draw offers to be
14705            made after you make your move but before you punch
14706            your clock.  Currently ICS doesn't let you do that;
14707            instead, you immediately punch your clock after making
14708            a move, but you can offer a draw at any time. */
14709
14710         SendToICS(ics_prefix);
14711         SendToICS("draw\n");
14712         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14713     } else if (cmailMsgLoaded) {
14714         if (currentMove == cmailOldMove &&
14715             commentList[cmailOldMove] != NULL &&
14716             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14717                    "Black offers a draw" : "White offers a draw")) {
14718             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14719             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14720         } else if (currentMove == cmailOldMove + 1) {
14721             char *offer = WhiteOnMove(cmailOldMove) ?
14722               "White offers a draw" : "Black offers a draw";
14723             AppendComment(currentMove, offer, TRUE);
14724             DisplayComment(currentMove - 1, offer);
14725             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14726         } else {
14727             DisplayError(_("You must make your move before offering a draw"), 0);
14728             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14729         }
14730     } else if (first.offeredDraw) {
14731         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14732     } else {
14733         if (first.sendDrawOffers) {
14734             SendToProgram("draw\n", &first);
14735             userOfferedDraw = TRUE;
14736         }
14737     }
14738 }
14739
14740 void
14741 AdjournEvent ()
14742 {
14743     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14744
14745     if (appData.icsActive) {
14746         SendToICS(ics_prefix);
14747         SendToICS("adjourn\n");
14748     } else {
14749         /* Currently GNU Chess doesn't offer or accept Adjourns */
14750     }
14751 }
14752
14753
14754 void
14755 AbortEvent ()
14756 {
14757     /* Offer Abort or accept pending Abort offer from opponent */
14758
14759     if (appData.icsActive) {
14760         SendToICS(ics_prefix);
14761         SendToICS("abort\n");
14762     } else {
14763         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14764     }
14765 }
14766
14767 void
14768 ResignEvent ()
14769 {
14770     /* Resign.  You can do this even if it's not your turn. */
14771
14772     if (appData.icsActive) {
14773         SendToICS(ics_prefix);
14774         SendToICS("resign\n");
14775     } else {
14776         switch (gameMode) {
14777           case MachinePlaysWhite:
14778             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14779             break;
14780           case MachinePlaysBlack:
14781             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14782             break;
14783           case EditGame:
14784             if (cmailMsgLoaded) {
14785                 TruncateGame();
14786                 if (WhiteOnMove(cmailOldMove)) {
14787                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14788                 } else {
14789                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14790                 }
14791                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14792             }
14793             break;
14794           default:
14795             break;
14796         }
14797     }
14798 }
14799
14800
14801 void
14802 StopObservingEvent ()
14803 {
14804     /* Stop observing current games */
14805     SendToICS(ics_prefix);
14806     SendToICS("unobserve\n");
14807 }
14808
14809 void
14810 StopExaminingEvent ()
14811 {
14812     /* Stop observing current game */
14813     SendToICS(ics_prefix);
14814     SendToICS("unexamine\n");
14815 }
14816
14817 void
14818 ForwardInner (int target)
14819 {
14820     int limit; int oldSeekGraphUp = seekGraphUp;
14821
14822     if (appData.debugMode)
14823         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14824                 target, currentMove, forwardMostMove);
14825
14826     if (gameMode == EditPosition)
14827       return;
14828
14829     seekGraphUp = FALSE;
14830     MarkTargetSquares(1);
14831
14832     if (gameMode == PlayFromGameFile && !pausing)
14833       PauseEvent();
14834
14835     if (gameMode == IcsExamining && pausing)
14836       limit = pauseExamForwardMostMove;
14837     else
14838       limit = forwardMostMove;
14839
14840     if (target > limit) target = limit;
14841
14842     if (target > 0 && moveList[target - 1][0]) {
14843         int fromX, fromY, toX, toY;
14844         toX = moveList[target - 1][2] - AAA;
14845         toY = moveList[target - 1][3] - ONE;
14846         if (moveList[target - 1][1] == '@') {
14847             if (appData.highlightLastMove) {
14848                 SetHighlights(-1, -1, toX, toY);
14849             }
14850         } else {
14851             fromX = moveList[target - 1][0] - AAA;
14852             fromY = moveList[target - 1][1] - ONE;
14853             if (target == currentMove + 1) {
14854                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14855             }
14856             if (appData.highlightLastMove) {
14857                 SetHighlights(fromX, fromY, toX, toY);
14858             }
14859         }
14860     }
14861     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14862         gameMode == Training || gameMode == PlayFromGameFile ||
14863         gameMode == AnalyzeFile) {
14864         while (currentMove < target) {
14865             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14866             SendMoveToProgram(currentMove++, &first);
14867         }
14868     } else {
14869         currentMove = target;
14870     }
14871
14872     if (gameMode == EditGame || gameMode == EndOfGame) {
14873         whiteTimeRemaining = timeRemaining[0][currentMove];
14874         blackTimeRemaining = timeRemaining[1][currentMove];
14875     }
14876     DisplayBothClocks();
14877     DisplayMove(currentMove - 1);
14878     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14879     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14880     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14881         DisplayComment(currentMove - 1, commentList[currentMove]);
14882     }
14883     ClearMap(); // [HGM] exclude: invalidate map
14884 }
14885
14886
14887 void
14888 ForwardEvent ()
14889 {
14890     if (gameMode == IcsExamining && !pausing) {
14891         SendToICS(ics_prefix);
14892         SendToICS("forward\n");
14893     } else {
14894         ForwardInner(currentMove + 1);
14895     }
14896 }
14897
14898 void
14899 ToEndEvent ()
14900 {
14901     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14902         /* to optimze, we temporarily turn off analysis mode while we feed
14903          * the remaining moves to the engine. Otherwise we get analysis output
14904          * after each move.
14905          */
14906         if (first.analysisSupport) {
14907           SendToProgram("exit\nforce\n", &first);
14908           first.analyzing = FALSE;
14909         }
14910     }
14911
14912     if (gameMode == IcsExamining && !pausing) {
14913         SendToICS(ics_prefix);
14914         SendToICS("forward 999999\n");
14915     } else {
14916         ForwardInner(forwardMostMove);
14917     }
14918
14919     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14920         /* we have fed all the moves, so reactivate analysis mode */
14921         SendToProgram("analyze\n", &first);
14922         first.analyzing = TRUE;
14923         /*first.maybeThinking = TRUE;*/
14924         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14925     }
14926 }
14927
14928 void
14929 BackwardInner (int target)
14930 {
14931     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14932
14933     if (appData.debugMode)
14934         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14935                 target, currentMove, forwardMostMove);
14936
14937     if (gameMode == EditPosition) return;
14938     seekGraphUp = FALSE;
14939     MarkTargetSquares(1);
14940     if (currentMove <= backwardMostMove) {
14941         ClearHighlights();
14942         DrawPosition(full_redraw, boards[currentMove]);
14943         return;
14944     }
14945     if (gameMode == PlayFromGameFile && !pausing)
14946       PauseEvent();
14947
14948     if (moveList[target][0]) {
14949         int fromX, fromY, toX, toY;
14950         toX = moveList[target][2] - AAA;
14951         toY = moveList[target][3] - ONE;
14952         if (moveList[target][1] == '@') {
14953             if (appData.highlightLastMove) {
14954                 SetHighlights(-1, -1, toX, toY);
14955             }
14956         } else {
14957             fromX = moveList[target][0] - AAA;
14958             fromY = moveList[target][1] - ONE;
14959             if (target == currentMove - 1) {
14960                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14961             }
14962             if (appData.highlightLastMove) {
14963                 SetHighlights(fromX, fromY, toX, toY);
14964             }
14965         }
14966     }
14967     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14968         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14969         while (currentMove > target) {
14970             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14971                 // null move cannot be undone. Reload program with move history before it.
14972                 int i;
14973                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14974                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14975                 }
14976                 SendBoard(&first, i);
14977               if(second.analyzing) SendBoard(&second, i);
14978                 for(currentMove=i; currentMove<target; currentMove++) {
14979                     SendMoveToProgram(currentMove, &first);
14980                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14981                 }
14982                 break;
14983             }
14984             SendToBoth("undo\n");
14985             currentMove--;
14986         }
14987     } else {
14988         currentMove = target;
14989     }
14990
14991     if (gameMode == EditGame || gameMode == EndOfGame) {
14992         whiteTimeRemaining = timeRemaining[0][currentMove];
14993         blackTimeRemaining = timeRemaining[1][currentMove];
14994     }
14995     DisplayBothClocks();
14996     DisplayMove(currentMove - 1);
14997     DrawPosition(full_redraw, boards[currentMove]);
14998     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14999     // [HGM] PV info: routine tests if comment empty
15000     DisplayComment(currentMove - 1, commentList[currentMove]);
15001     ClearMap(); // [HGM] exclude: invalidate map
15002 }
15003
15004 void
15005 BackwardEvent ()
15006 {
15007     if (gameMode == IcsExamining && !pausing) {
15008         SendToICS(ics_prefix);
15009         SendToICS("backward\n");
15010     } else {
15011         BackwardInner(currentMove - 1);
15012     }
15013 }
15014
15015 void
15016 ToStartEvent ()
15017 {
15018     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15019         /* to optimize, we temporarily turn off analysis mode while we undo
15020          * all the moves. Otherwise we get analysis output after each undo.
15021          */
15022         if (first.analysisSupport) {
15023           SendToProgram("exit\nforce\n", &first);
15024           first.analyzing = FALSE;
15025         }
15026     }
15027
15028     if (gameMode == IcsExamining && !pausing) {
15029         SendToICS(ics_prefix);
15030         SendToICS("backward 999999\n");
15031     } else {
15032         BackwardInner(backwardMostMove);
15033     }
15034
15035     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15036         /* we have fed all the moves, so reactivate analysis mode */
15037         SendToProgram("analyze\n", &first);
15038         first.analyzing = TRUE;
15039         /*first.maybeThinking = TRUE;*/
15040         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15041     }
15042 }
15043
15044 void
15045 ToNrEvent (int to)
15046 {
15047   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15048   if (to >= forwardMostMove) to = forwardMostMove;
15049   if (to <= backwardMostMove) to = backwardMostMove;
15050   if (to < currentMove) {
15051     BackwardInner(to);
15052   } else {
15053     ForwardInner(to);
15054   }
15055 }
15056
15057 void
15058 RevertEvent (Boolean annotate)
15059 {
15060     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15061         return;
15062     }
15063     if (gameMode != IcsExamining) {
15064         DisplayError(_("You are not examining a game"), 0);
15065         return;
15066     }
15067     if (pausing) {
15068         DisplayError(_("You can't revert while pausing"), 0);
15069         return;
15070     }
15071     SendToICS(ics_prefix);
15072     SendToICS("revert\n");
15073 }
15074
15075 void
15076 RetractMoveEvent ()
15077 {
15078     switch (gameMode) {
15079       case MachinePlaysWhite:
15080       case MachinePlaysBlack:
15081         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15082             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15083             return;
15084         }
15085         if (forwardMostMove < 2) return;
15086         currentMove = forwardMostMove = forwardMostMove - 2;
15087         whiteTimeRemaining = timeRemaining[0][currentMove];
15088         blackTimeRemaining = timeRemaining[1][currentMove];
15089         DisplayBothClocks();
15090         DisplayMove(currentMove - 1);
15091         ClearHighlights();/*!! could figure this out*/
15092         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15093         SendToProgram("remove\n", &first);
15094         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15095         break;
15096
15097       case BeginningOfGame:
15098       default:
15099         break;
15100
15101       case IcsPlayingWhite:
15102       case IcsPlayingBlack:
15103         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15104             SendToICS(ics_prefix);
15105             SendToICS("takeback 2\n");
15106         } else {
15107             SendToICS(ics_prefix);
15108             SendToICS("takeback 1\n");
15109         }
15110         break;
15111     }
15112 }
15113
15114 void
15115 MoveNowEvent ()
15116 {
15117     ChessProgramState *cps;
15118
15119     switch (gameMode) {
15120       case MachinePlaysWhite:
15121         if (!WhiteOnMove(forwardMostMove)) {
15122             DisplayError(_("It is your turn"), 0);
15123             return;
15124         }
15125         cps = &first;
15126         break;
15127       case MachinePlaysBlack:
15128         if (WhiteOnMove(forwardMostMove)) {
15129             DisplayError(_("It is your turn"), 0);
15130             return;
15131         }
15132         cps = &first;
15133         break;
15134       case TwoMachinesPlay:
15135         if (WhiteOnMove(forwardMostMove) ==
15136             (first.twoMachinesColor[0] == 'w')) {
15137             cps = &first;
15138         } else {
15139             cps = &second;
15140         }
15141         break;
15142       case BeginningOfGame:
15143       default:
15144         return;
15145     }
15146     SendToProgram("?\n", cps);
15147 }
15148
15149 void
15150 TruncateGameEvent ()
15151 {
15152     EditGameEvent();
15153     if (gameMode != EditGame) return;
15154     TruncateGame();
15155 }
15156
15157 void
15158 TruncateGame ()
15159 {
15160     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15161     if (forwardMostMove > currentMove) {
15162         if (gameInfo.resultDetails != NULL) {
15163             free(gameInfo.resultDetails);
15164             gameInfo.resultDetails = NULL;
15165             gameInfo.result = GameUnfinished;
15166         }
15167         forwardMostMove = currentMove;
15168         HistorySet(parseList, backwardMostMove, forwardMostMove,
15169                    currentMove-1);
15170     }
15171 }
15172
15173 void
15174 HintEvent ()
15175 {
15176     if (appData.noChessProgram) return;
15177     switch (gameMode) {
15178       case MachinePlaysWhite:
15179         if (WhiteOnMove(forwardMostMove)) {
15180             DisplayError(_("Wait until your turn"), 0);
15181             return;
15182         }
15183         break;
15184       case BeginningOfGame:
15185       case MachinePlaysBlack:
15186         if (!WhiteOnMove(forwardMostMove)) {
15187             DisplayError(_("Wait until your turn"), 0);
15188             return;
15189         }
15190         break;
15191       default:
15192         DisplayError(_("No hint available"), 0);
15193         return;
15194     }
15195     SendToProgram("hint\n", &first);
15196     hintRequested = TRUE;
15197 }
15198
15199 void
15200 CreateBookEvent ()
15201 {
15202     ListGame * lg = (ListGame *) gameList.head;
15203     FILE *f;
15204     int nItem;
15205     static int secondTime = FALSE;
15206
15207     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15208         DisplayError(_("Game list not loaded or empty"), 0);
15209         return;
15210     }
15211
15212     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15213         fclose(f);
15214         secondTime++;
15215         DisplayNote(_("Book file exists! Try again for overwrite."));
15216         return;
15217     }
15218
15219     creatingBook = TRUE;
15220     secondTime = FALSE;
15221
15222     /* Get list size */
15223     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15224         LoadGame(f, nItem, "", TRUE);
15225         AddGameToBook(TRUE);
15226         lg = (ListGame *) lg->node.succ;
15227     }
15228
15229     creatingBook = FALSE;
15230     FlushBook();
15231 }
15232
15233 void
15234 BookEvent ()
15235 {
15236     if (appData.noChessProgram) return;
15237     switch (gameMode) {
15238       case MachinePlaysWhite:
15239         if (WhiteOnMove(forwardMostMove)) {
15240             DisplayError(_("Wait until your turn"), 0);
15241             return;
15242         }
15243         break;
15244       case BeginningOfGame:
15245       case MachinePlaysBlack:
15246         if (!WhiteOnMove(forwardMostMove)) {
15247             DisplayError(_("Wait until your turn"), 0);
15248             return;
15249         }
15250         break;
15251       case EditPosition:
15252         EditPositionDone(TRUE);
15253         break;
15254       case TwoMachinesPlay:
15255         return;
15256       default:
15257         break;
15258     }
15259     SendToProgram("bk\n", &first);
15260     bookOutput[0] = NULLCHAR;
15261     bookRequested = TRUE;
15262 }
15263
15264 void
15265 AboutGameEvent ()
15266 {
15267     char *tags = PGNTags(&gameInfo);
15268     TagsPopUp(tags, CmailMsg());
15269     free(tags);
15270 }
15271
15272 /* end button procedures */
15273
15274 void
15275 PrintPosition (FILE *fp, int move)
15276 {
15277     int i, j;
15278
15279     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15280         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15281             char c = PieceToChar(boards[move][i][j]);
15282             fputc(c == 'x' ? '.' : c, fp);
15283             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15284         }
15285     }
15286     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15287       fprintf(fp, "white to play\n");
15288     else
15289       fprintf(fp, "black to play\n");
15290 }
15291
15292 void
15293 PrintOpponents (FILE *fp)
15294 {
15295     if (gameInfo.white != NULL) {
15296         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15297     } else {
15298         fprintf(fp, "\n");
15299     }
15300 }
15301
15302 /* Find last component of program's own name, using some heuristics */
15303 void
15304 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15305 {
15306     char *p, *q, c;
15307     int local = (strcmp(host, "localhost") == 0);
15308     while (!local && (p = strchr(prog, ';')) != NULL) {
15309         p++;
15310         while (*p == ' ') p++;
15311         prog = p;
15312     }
15313     if (*prog == '"' || *prog == '\'') {
15314         q = strchr(prog + 1, *prog);
15315     } else {
15316         q = strchr(prog, ' ');
15317     }
15318     if (q == NULL) q = prog + strlen(prog);
15319     p = q;
15320     while (p >= prog && *p != '/' && *p != '\\') p--;
15321     p++;
15322     if(p == prog && *p == '"') p++;
15323     c = *q; *q = 0;
15324     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15325     memcpy(buf, p, q - p);
15326     buf[q - p] = NULLCHAR;
15327     if (!local) {
15328         strcat(buf, "@");
15329         strcat(buf, host);
15330     }
15331 }
15332
15333 char *
15334 TimeControlTagValue ()
15335 {
15336     char buf[MSG_SIZ];
15337     if (!appData.clockMode) {
15338       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15339     } else if (movesPerSession > 0) {
15340       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15341     } else if (timeIncrement == 0) {
15342       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15343     } else {
15344       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15345     }
15346     return StrSave(buf);
15347 }
15348
15349 void
15350 SetGameInfo ()
15351 {
15352     /* This routine is used only for certain modes */
15353     VariantClass v = gameInfo.variant;
15354     ChessMove r = GameUnfinished;
15355     char *p = NULL;
15356
15357     if(keepInfo) return;
15358
15359     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15360         r = gameInfo.result;
15361         p = gameInfo.resultDetails;
15362         gameInfo.resultDetails = NULL;
15363     }
15364     ClearGameInfo(&gameInfo);
15365     gameInfo.variant = v;
15366
15367     switch (gameMode) {
15368       case MachinePlaysWhite:
15369         gameInfo.event = StrSave( appData.pgnEventHeader );
15370         gameInfo.site = StrSave(HostName());
15371         gameInfo.date = PGNDate();
15372         gameInfo.round = StrSave("-");
15373         gameInfo.white = StrSave(first.tidy);
15374         gameInfo.black = StrSave(UserName());
15375         gameInfo.timeControl = TimeControlTagValue();
15376         break;
15377
15378       case MachinePlaysBlack:
15379         gameInfo.event = StrSave( appData.pgnEventHeader );
15380         gameInfo.site = StrSave(HostName());
15381         gameInfo.date = PGNDate();
15382         gameInfo.round = StrSave("-");
15383         gameInfo.white = StrSave(UserName());
15384         gameInfo.black = StrSave(first.tidy);
15385         gameInfo.timeControl = TimeControlTagValue();
15386         break;
15387
15388       case TwoMachinesPlay:
15389         gameInfo.event = StrSave( appData.pgnEventHeader );
15390         gameInfo.site = StrSave(HostName());
15391         gameInfo.date = PGNDate();
15392         if (roundNr > 0) {
15393             char buf[MSG_SIZ];
15394             snprintf(buf, MSG_SIZ, "%d", roundNr);
15395             gameInfo.round = StrSave(buf);
15396         } else {
15397             gameInfo.round = StrSave("-");
15398         }
15399         if (first.twoMachinesColor[0] == 'w') {
15400             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15401             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15402         } else {
15403             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15404             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15405         }
15406         gameInfo.timeControl = TimeControlTagValue();
15407         break;
15408
15409       case EditGame:
15410         gameInfo.event = StrSave("Edited game");
15411         gameInfo.site = StrSave(HostName());
15412         gameInfo.date = PGNDate();
15413         gameInfo.round = StrSave("-");
15414         gameInfo.white = StrSave("-");
15415         gameInfo.black = StrSave("-");
15416         gameInfo.result = r;
15417         gameInfo.resultDetails = p;
15418         break;
15419
15420       case EditPosition:
15421         gameInfo.event = StrSave("Edited position");
15422         gameInfo.site = StrSave(HostName());
15423         gameInfo.date = PGNDate();
15424         gameInfo.round = StrSave("-");
15425         gameInfo.white = StrSave("-");
15426         gameInfo.black = StrSave("-");
15427         break;
15428
15429       case IcsPlayingWhite:
15430       case IcsPlayingBlack:
15431       case IcsObserving:
15432       case IcsExamining:
15433         break;
15434
15435       case PlayFromGameFile:
15436         gameInfo.event = StrSave("Game from non-PGN file");
15437         gameInfo.site = StrSave(HostName());
15438         gameInfo.date = PGNDate();
15439         gameInfo.round = StrSave("-");
15440         gameInfo.white = StrSave("?");
15441         gameInfo.black = StrSave("?");
15442         break;
15443
15444       default:
15445         break;
15446     }
15447 }
15448
15449 void
15450 ReplaceComment (int index, char *text)
15451 {
15452     int len;
15453     char *p;
15454     float score;
15455
15456     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15457        pvInfoList[index-1].depth == len &&
15458        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15459        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15460     while (*text == '\n') text++;
15461     len = strlen(text);
15462     while (len > 0 && text[len - 1] == '\n') len--;
15463
15464     if (commentList[index] != NULL)
15465       free(commentList[index]);
15466
15467     if (len == 0) {
15468         commentList[index] = NULL;
15469         return;
15470     }
15471   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15472       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15473       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15474     commentList[index] = (char *) malloc(len + 2);
15475     strncpy(commentList[index], text, len);
15476     commentList[index][len] = '\n';
15477     commentList[index][len + 1] = NULLCHAR;
15478   } else {
15479     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15480     char *p;
15481     commentList[index] = (char *) malloc(len + 7);
15482     safeStrCpy(commentList[index], "{\n", 3);
15483     safeStrCpy(commentList[index]+2, text, len+1);
15484     commentList[index][len+2] = NULLCHAR;
15485     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15486     strcat(commentList[index], "\n}\n");
15487   }
15488 }
15489
15490 void
15491 CrushCRs (char *text)
15492 {
15493   char *p = text;
15494   char *q = text;
15495   char ch;
15496
15497   do {
15498     ch = *p++;
15499     if (ch == '\r') continue;
15500     *q++ = ch;
15501   } while (ch != '\0');
15502 }
15503
15504 void
15505 AppendComment (int index, char *text, Boolean addBraces)
15506 /* addBraces  tells if we should add {} */
15507 {
15508     int oldlen, len;
15509     char *old;
15510
15511 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15512     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15513
15514     CrushCRs(text);
15515     while (*text == '\n') text++;
15516     len = strlen(text);
15517     while (len > 0 && text[len - 1] == '\n') len--;
15518     text[len] = NULLCHAR;
15519
15520     if (len == 0) return;
15521
15522     if (commentList[index] != NULL) {
15523       Boolean addClosingBrace = addBraces;
15524         old = commentList[index];
15525         oldlen = strlen(old);
15526         while(commentList[index][oldlen-1] ==  '\n')
15527           commentList[index][--oldlen] = NULLCHAR;
15528         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15529         safeStrCpy(commentList[index], old, oldlen + len + 6);
15530         free(old);
15531         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15532         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15533           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15534           while (*text == '\n') { text++; len--; }
15535           commentList[index][--oldlen] = NULLCHAR;
15536       }
15537         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15538         else          strcat(commentList[index], "\n");
15539         strcat(commentList[index], text);
15540         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15541         else          strcat(commentList[index], "\n");
15542     } else {
15543         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15544         if(addBraces)
15545           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15546         else commentList[index][0] = NULLCHAR;
15547         strcat(commentList[index], text);
15548         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15549         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15550     }
15551 }
15552
15553 static char *
15554 FindStr (char * text, char * sub_text)
15555 {
15556     char * result = strstr( text, sub_text );
15557
15558     if( result != NULL ) {
15559         result += strlen( sub_text );
15560     }
15561
15562     return result;
15563 }
15564
15565 /* [AS] Try to extract PV info from PGN comment */
15566 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15567 char *
15568 GetInfoFromComment (int index, char * text)
15569 {
15570     char * sep = text, *p;
15571
15572     if( text != NULL && index > 0 ) {
15573         int score = 0;
15574         int depth = 0;
15575         int time = -1, sec = 0, deci;
15576         char * s_eval = FindStr( text, "[%eval " );
15577         char * s_emt = FindStr( text, "[%emt " );
15578
15579         if( s_eval != NULL || s_emt != NULL ) {
15580             /* New style */
15581             char delim;
15582
15583             if( s_eval != NULL ) {
15584                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15585                     return text;
15586                 }
15587
15588                 if( delim != ']' ) {
15589                     return text;
15590                 }
15591             }
15592
15593             if( s_emt != NULL ) {
15594             }
15595                 return text;
15596         }
15597         else {
15598             /* We expect something like: [+|-]nnn.nn/dd */
15599             int score_lo = 0;
15600
15601             if(*text != '{') return text; // [HGM] braces: must be normal comment
15602
15603             sep = strchr( text, '/' );
15604             if( sep == NULL || sep < (text+4) ) {
15605                 return text;
15606             }
15607
15608             p = text;
15609             if(p[1] == '(') { // comment starts with PV
15610                p = strchr(p, ')'); // locate end of PV
15611                if(p == NULL || sep < p+5) return text;
15612                // at this point we have something like "{(.*) +0.23/6 ..."
15613                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15614                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15615                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15616             }
15617             time = -1; sec = -1; deci = -1;
15618             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15619                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15620                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15621                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15622                 return text;
15623             }
15624
15625             if( score_lo < 0 || score_lo >= 100 ) {
15626                 return text;
15627             }
15628
15629             if(sec >= 0) time = 600*time + 10*sec; else
15630             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15631
15632             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15633
15634             /* [HGM] PV time: now locate end of PV info */
15635             while( *++sep >= '0' && *sep <= '9'); // strip depth
15636             if(time >= 0)
15637             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15638             if(sec >= 0)
15639             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15640             if(deci >= 0)
15641             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15642             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15643         }
15644
15645         if( depth <= 0 ) {
15646             return text;
15647         }
15648
15649         if( time < 0 ) {
15650             time = -1;
15651         }
15652
15653         pvInfoList[index-1].depth = depth;
15654         pvInfoList[index-1].score = score;
15655         pvInfoList[index-1].time  = 10*time; // centi-sec
15656         if(*sep == '}') *sep = 0; else *--sep = '{';
15657         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15658     }
15659     return sep;
15660 }
15661
15662 void
15663 SendToProgram (char *message, ChessProgramState *cps)
15664 {
15665     int count, outCount, error;
15666     char buf[MSG_SIZ];
15667
15668     if (cps->pr == NoProc) return;
15669     Attention(cps);
15670
15671     if (appData.debugMode) {
15672         TimeMark now;
15673         GetTimeMark(&now);
15674         fprintf(debugFP, "%ld >%-6s: %s",
15675                 SubtractTimeMarks(&now, &programStartTime),
15676                 cps->which, message);
15677         if(serverFP)
15678             fprintf(serverFP, "%ld >%-6s: %s",
15679                 SubtractTimeMarks(&now, &programStartTime),
15680                 cps->which, message), fflush(serverFP);
15681     }
15682
15683     count = strlen(message);
15684     outCount = OutputToProcess(cps->pr, message, count, &error);
15685     if (outCount < count && !exiting
15686                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15687       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15688       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15689         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15690             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15691                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15692                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15693                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15694             } else {
15695                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15696                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15697                 gameInfo.result = res;
15698             }
15699             gameInfo.resultDetails = StrSave(buf);
15700         }
15701         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15702         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15703     }
15704 }
15705
15706 void
15707 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15708 {
15709     char *end_str;
15710     char buf[MSG_SIZ];
15711     ChessProgramState *cps = (ChessProgramState *)closure;
15712
15713     if (isr != cps->isr) return; /* Killed intentionally */
15714     if (count <= 0) {
15715         if (count == 0) {
15716             RemoveInputSource(cps->isr);
15717             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15718                     _(cps->which), cps->program);
15719             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15720             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15721                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15722                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15723                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15724                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15725                 } else {
15726                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15727                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15728                     gameInfo.result = res;
15729                 }
15730                 gameInfo.resultDetails = StrSave(buf);
15731             }
15732             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15733             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15734         } else {
15735             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15736                     _(cps->which), cps->program);
15737             RemoveInputSource(cps->isr);
15738
15739             /* [AS] Program is misbehaving badly... kill it */
15740             if( count == -2 ) {
15741                 DestroyChildProcess( cps->pr, 9 );
15742                 cps->pr = NoProc;
15743             }
15744
15745             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15746         }
15747         return;
15748     }
15749
15750     if ((end_str = strchr(message, '\r')) != NULL)
15751       *end_str = NULLCHAR;
15752     if ((end_str = strchr(message, '\n')) != NULL)
15753       *end_str = NULLCHAR;
15754
15755     if (appData.debugMode) {
15756         TimeMark now; int print = 1;
15757         char *quote = ""; char c; int i;
15758
15759         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15760                 char start = message[0];
15761                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15762                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15763                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15764                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15765                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15766                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15767                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15768                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15769                    sscanf(message, "hint: %c", &c)!=1 &&
15770                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15771                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15772                     print = (appData.engineComments >= 2);
15773                 }
15774                 message[0] = start; // restore original message
15775         }
15776         if(print) {
15777                 GetTimeMark(&now);
15778                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15779                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15780                         quote,
15781                         message);
15782                 if(serverFP)
15783                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15784                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15785                         quote,
15786                         message), fflush(serverFP);
15787         }
15788     }
15789
15790     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15791     if (appData.icsEngineAnalyze) {
15792         if (strstr(message, "whisper") != NULL ||
15793              strstr(message, "kibitz") != NULL ||
15794             strstr(message, "tellics") != NULL) return;
15795     }
15796
15797     HandleMachineMove(message, cps);
15798 }
15799
15800
15801 void
15802 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15803 {
15804     char buf[MSG_SIZ];
15805     int seconds;
15806
15807     if( timeControl_2 > 0 ) {
15808         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15809             tc = timeControl_2;
15810         }
15811     }
15812     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15813     inc /= cps->timeOdds;
15814     st  /= cps->timeOdds;
15815
15816     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15817
15818     if (st > 0) {
15819       /* Set exact time per move, normally using st command */
15820       if (cps->stKludge) {
15821         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15822         seconds = st % 60;
15823         if (seconds == 0) {
15824           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15825         } else {
15826           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15827         }
15828       } else {
15829         snprintf(buf, MSG_SIZ, "st %d\n", st);
15830       }
15831     } else {
15832       /* Set conventional or incremental time control, using level command */
15833       if (seconds == 0) {
15834         /* Note old gnuchess bug -- minutes:seconds used to not work.
15835            Fixed in later versions, but still avoid :seconds
15836            when seconds is 0. */
15837         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15838       } else {
15839         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15840                  seconds, inc/1000.);
15841       }
15842     }
15843     SendToProgram(buf, cps);
15844
15845     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15846     /* Orthogonally, limit search to given depth */
15847     if (sd > 0) {
15848       if (cps->sdKludge) {
15849         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15850       } else {
15851         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15852       }
15853       SendToProgram(buf, cps);
15854     }
15855
15856     if(cps->nps >= 0) { /* [HGM] nps */
15857         if(cps->supportsNPS == FALSE)
15858           cps->nps = -1; // don't use if engine explicitly says not supported!
15859         else {
15860           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15861           SendToProgram(buf, cps);
15862         }
15863     }
15864 }
15865
15866 ChessProgramState *
15867 WhitePlayer ()
15868 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15869 {
15870     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15871        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15872         return &second;
15873     return &first;
15874 }
15875
15876 void
15877 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15878 {
15879     char message[MSG_SIZ];
15880     long time, otime;
15881
15882     /* Note: this routine must be called when the clocks are stopped
15883        or when they have *just* been set or switched; otherwise
15884        it will be off by the time since the current tick started.
15885     */
15886     if (machineWhite) {
15887         time = whiteTimeRemaining / 10;
15888         otime = blackTimeRemaining / 10;
15889     } else {
15890         time = blackTimeRemaining / 10;
15891         otime = whiteTimeRemaining / 10;
15892     }
15893     /* [HGM] translate opponent's time by time-odds factor */
15894     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15895
15896     if (time <= 0) time = 1;
15897     if (otime <= 0) otime = 1;
15898
15899     snprintf(message, MSG_SIZ, "time %ld\n", time);
15900     SendToProgram(message, cps);
15901
15902     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15903     SendToProgram(message, cps);
15904 }
15905
15906 int
15907 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15908 {
15909   char buf[MSG_SIZ];
15910   int len = strlen(name);
15911   int val;
15912
15913   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15914     (*p) += len + 1;
15915     sscanf(*p, "%d", &val);
15916     *loc = (val != 0);
15917     while (**p && **p != ' ')
15918       (*p)++;
15919     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15920     SendToProgram(buf, cps);
15921     return TRUE;
15922   }
15923   return FALSE;
15924 }
15925
15926 int
15927 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15928 {
15929   char buf[MSG_SIZ];
15930   int len = strlen(name);
15931   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15932     (*p) += len + 1;
15933     sscanf(*p, "%d", loc);
15934     while (**p && **p != ' ') (*p)++;
15935     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15936     SendToProgram(buf, cps);
15937     return TRUE;
15938   }
15939   return FALSE;
15940 }
15941
15942 int
15943 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15944 {
15945   char buf[MSG_SIZ];
15946   int len = strlen(name);
15947   if (strncmp((*p), name, len) == 0
15948       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15949     (*p) += len + 2;
15950     sscanf(*p, "%[^\"]", loc);
15951     while (**p && **p != '\"') (*p)++;
15952     if (**p == '\"') (*p)++;
15953     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15954     SendToProgram(buf, cps);
15955     return TRUE;
15956   }
15957   return FALSE;
15958 }
15959
15960 int
15961 ParseOption (Option *opt, ChessProgramState *cps)
15962 // [HGM] options: process the string that defines an engine option, and determine
15963 // name, type, default value, and allowed value range
15964 {
15965         char *p, *q, buf[MSG_SIZ];
15966         int n, min = (-1)<<31, max = 1<<31, def;
15967
15968         if(p = strstr(opt->name, " -spin ")) {
15969             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15970             if(max < min) max = min; // enforce consistency
15971             if(def < min) def = min;
15972             if(def > max) def = max;
15973             opt->value = def;
15974             opt->min = min;
15975             opt->max = max;
15976             opt->type = Spin;
15977         } else if((p = strstr(opt->name, " -slider "))) {
15978             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15979             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15980             if(max < min) max = min; // enforce consistency
15981             if(def < min) def = min;
15982             if(def > max) def = max;
15983             opt->value = def;
15984             opt->min = min;
15985             opt->max = max;
15986             opt->type = Spin; // Slider;
15987         } else if((p = strstr(opt->name, " -string "))) {
15988             opt->textValue = p+9;
15989             opt->type = TextBox;
15990         } else if((p = strstr(opt->name, " -file "))) {
15991             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15992             opt->textValue = p+7;
15993             opt->type = FileName; // FileName;
15994         } else if((p = strstr(opt->name, " -path "))) {
15995             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15996             opt->textValue = p+7;
15997             opt->type = PathName; // PathName;
15998         } else if(p = strstr(opt->name, " -check ")) {
15999             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16000             opt->value = (def != 0);
16001             opt->type = CheckBox;
16002         } else if(p = strstr(opt->name, " -combo ")) {
16003             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16004             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16005             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16006             opt->value = n = 0;
16007             while(q = StrStr(q, " /// ")) {
16008                 n++; *q = 0;    // count choices, and null-terminate each of them
16009                 q += 5;
16010                 if(*q == '*') { // remember default, which is marked with * prefix
16011                     q++;
16012                     opt->value = n;
16013                 }
16014                 cps->comboList[cps->comboCnt++] = q;
16015             }
16016             cps->comboList[cps->comboCnt++] = NULL;
16017             opt->max = n + 1;
16018             opt->type = ComboBox;
16019         } else if(p = strstr(opt->name, " -button")) {
16020             opt->type = Button;
16021         } else if(p = strstr(opt->name, " -save")) {
16022             opt->type = SaveButton;
16023         } else return FALSE;
16024         *p = 0; // terminate option name
16025         // now look if the command-line options define a setting for this engine option.
16026         if(cps->optionSettings && cps->optionSettings[0])
16027             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16028         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16029           snprintf(buf, MSG_SIZ, "option %s", p);
16030                 if(p = strstr(buf, ",")) *p = 0;
16031                 if(q = strchr(buf, '=')) switch(opt->type) {
16032                     case ComboBox:
16033                         for(n=0; n<opt->max; n++)
16034                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16035                         break;
16036                     case TextBox:
16037                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16038                         break;
16039                     case Spin:
16040                     case CheckBox:
16041                         opt->value = atoi(q+1);
16042                     default:
16043                         break;
16044                 }
16045                 strcat(buf, "\n");
16046                 SendToProgram(buf, cps);
16047         }
16048         return TRUE;
16049 }
16050
16051 void
16052 FeatureDone (ChessProgramState *cps, int val)
16053 {
16054   DelayedEventCallback cb = GetDelayedEvent();
16055   if ((cb == InitBackEnd3 && cps == &first) ||
16056       (cb == SettingsMenuIfReady && cps == &second) ||
16057       (cb == LoadEngine) ||
16058       (cb == TwoMachinesEventIfReady)) {
16059     CancelDelayedEvent();
16060     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16061   }
16062   cps->initDone = val;
16063   if(val) cps->reload = FALSE;
16064 }
16065
16066 /* Parse feature command from engine */
16067 void
16068 ParseFeatures (char *args, ChessProgramState *cps)
16069 {
16070   char *p = args;
16071   char *q;
16072   int val;
16073   char buf[MSG_SIZ];
16074
16075   for (;;) {
16076     while (*p == ' ') p++;
16077     if (*p == NULLCHAR) return;
16078
16079     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16080     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16081     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16082     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16083     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16084     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16085     if (BoolFeature(&p, "reuse", &val, cps)) {
16086       /* Engine can disable reuse, but can't enable it if user said no */
16087       if (!val) cps->reuse = FALSE;
16088       continue;
16089     }
16090     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16091     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16092       if (gameMode == TwoMachinesPlay) {
16093         DisplayTwoMachinesTitle();
16094       } else {
16095         DisplayTitle("");
16096       }
16097       continue;
16098     }
16099     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16100     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16101     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16102     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16103     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16104     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16105     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16106     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16107     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16108     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16109     if (IntFeature(&p, "done", &val, cps)) {
16110       FeatureDone(cps, val);
16111       continue;
16112     }
16113     /* Added by Tord: */
16114     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16115     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16116     /* End of additions by Tord */
16117
16118     /* [HGM] added features: */
16119     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16120     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16121     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16122     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16123     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16124     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16125     if (StringFeature(&p, "option", buf, cps)) {
16126         if(cps->reload) continue; // we are reloading because of xreuse
16127         FREE(cps->option[cps->nrOptions].name);
16128         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16129         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16130         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16131           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16132             SendToProgram(buf, cps);
16133             continue;
16134         }
16135         if(cps->nrOptions >= MAX_OPTIONS) {
16136             cps->nrOptions--;
16137             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16138             DisplayError(buf, 0);
16139         }
16140         continue;
16141     }
16142     /* End of additions by HGM */
16143
16144     /* unknown feature: complain and skip */
16145     q = p;
16146     while (*q && *q != '=') q++;
16147     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16148     SendToProgram(buf, cps);
16149     p = q;
16150     if (*p == '=') {
16151       p++;
16152       if (*p == '\"') {
16153         p++;
16154         while (*p && *p != '\"') p++;
16155         if (*p == '\"') p++;
16156       } else {
16157         while (*p && *p != ' ') p++;
16158       }
16159     }
16160   }
16161
16162 }
16163
16164 void
16165 PeriodicUpdatesEvent (int newState)
16166 {
16167     if (newState == appData.periodicUpdates)
16168       return;
16169
16170     appData.periodicUpdates=newState;
16171
16172     /* Display type changes, so update it now */
16173 //    DisplayAnalysis();
16174
16175     /* Get the ball rolling again... */
16176     if (newState) {
16177         AnalysisPeriodicEvent(1);
16178         StartAnalysisClock();
16179     }
16180 }
16181
16182 void
16183 PonderNextMoveEvent (int newState)
16184 {
16185     if (newState == appData.ponderNextMove) return;
16186     if (gameMode == EditPosition) EditPositionDone(TRUE);
16187     if (newState) {
16188         SendToProgram("hard\n", &first);
16189         if (gameMode == TwoMachinesPlay) {
16190             SendToProgram("hard\n", &second);
16191         }
16192     } else {
16193         SendToProgram("easy\n", &first);
16194         thinkOutput[0] = NULLCHAR;
16195         if (gameMode == TwoMachinesPlay) {
16196             SendToProgram("easy\n", &second);
16197         }
16198     }
16199     appData.ponderNextMove = newState;
16200 }
16201
16202 void
16203 NewSettingEvent (int option, int *feature, char *command, int value)
16204 {
16205     char buf[MSG_SIZ];
16206
16207     if (gameMode == EditPosition) EditPositionDone(TRUE);
16208     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16209     if(feature == NULL || *feature) SendToProgram(buf, &first);
16210     if (gameMode == TwoMachinesPlay) {
16211         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16212     }
16213 }
16214
16215 void
16216 ShowThinkingEvent ()
16217 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16218 {
16219     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16220     int newState = appData.showThinking
16221         // [HGM] thinking: other features now need thinking output as well
16222         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16223
16224     if (oldState == newState) return;
16225     oldState = newState;
16226     if (gameMode == EditPosition) EditPositionDone(TRUE);
16227     if (oldState) {
16228         SendToProgram("post\n", &first);
16229         if (gameMode == TwoMachinesPlay) {
16230             SendToProgram("post\n", &second);
16231         }
16232     } else {
16233         SendToProgram("nopost\n", &first);
16234         thinkOutput[0] = NULLCHAR;
16235         if (gameMode == TwoMachinesPlay) {
16236             SendToProgram("nopost\n", &second);
16237         }
16238     }
16239 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16240 }
16241
16242 void
16243 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16244 {
16245   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16246   if (pr == NoProc) return;
16247   AskQuestion(title, question, replyPrefix, pr);
16248 }
16249
16250 void
16251 TypeInEvent (char firstChar)
16252 {
16253     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16254         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16255         gameMode == AnalyzeMode || gameMode == EditGame ||
16256         gameMode == EditPosition || gameMode == IcsExamining ||
16257         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16258         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16259                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16260                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16261         gameMode == Training) PopUpMoveDialog(firstChar);
16262 }
16263
16264 void
16265 TypeInDoneEvent (char *move)
16266 {
16267         Board board;
16268         int n, fromX, fromY, toX, toY;
16269         char promoChar;
16270         ChessMove moveType;
16271
16272         // [HGM] FENedit
16273         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16274                 EditPositionPasteFEN(move);
16275                 return;
16276         }
16277         // [HGM] movenum: allow move number to be typed in any mode
16278         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16279           ToNrEvent(2*n-1);
16280           return;
16281         }
16282         // undocumented kludge: allow command-line option to be typed in!
16283         // (potentially fatal, and does not implement the effect of the option.)
16284         // should only be used for options that are values on which future decisions will be made,
16285         // and definitely not on options that would be used during initialization.
16286         if(strstr(move, "!!! -") == move) {
16287             ParseArgsFromString(move+4);
16288             return;
16289         }
16290
16291       if (gameMode != EditGame && currentMove != forwardMostMove &&
16292         gameMode != Training) {
16293         DisplayMoveError(_("Displayed move is not current"));
16294       } else {
16295         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16296           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16297         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16298         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16299           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16300           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16301         } else {
16302           DisplayMoveError(_("Could not parse move"));
16303         }
16304       }
16305 }
16306
16307 void
16308 DisplayMove (int moveNumber)
16309 {
16310     char message[MSG_SIZ];
16311     char res[MSG_SIZ];
16312     char cpThinkOutput[MSG_SIZ];
16313
16314     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16315
16316     if (moveNumber == forwardMostMove - 1 ||
16317         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16318
16319         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16320
16321         if (strchr(cpThinkOutput, '\n')) {
16322             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16323         }
16324     } else {
16325         *cpThinkOutput = NULLCHAR;
16326     }
16327
16328     /* [AS] Hide thinking from human user */
16329     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16330         *cpThinkOutput = NULLCHAR;
16331         if( thinkOutput[0] != NULLCHAR ) {
16332             int i;
16333
16334             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16335                 cpThinkOutput[i] = '.';
16336             }
16337             cpThinkOutput[i] = NULLCHAR;
16338             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16339         }
16340     }
16341
16342     if (moveNumber == forwardMostMove - 1 &&
16343         gameInfo.resultDetails != NULL) {
16344         if (gameInfo.resultDetails[0] == NULLCHAR) {
16345           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16346         } else {
16347           snprintf(res, MSG_SIZ, " {%s} %s",
16348                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16349         }
16350     } else {
16351         res[0] = NULLCHAR;
16352     }
16353
16354     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16355         DisplayMessage(res, cpThinkOutput);
16356     } else {
16357       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16358                 WhiteOnMove(moveNumber) ? " " : ".. ",
16359                 parseList[moveNumber], res);
16360         DisplayMessage(message, cpThinkOutput);
16361     }
16362 }
16363
16364 void
16365 DisplayComment (int moveNumber, char *text)
16366 {
16367     char title[MSG_SIZ];
16368
16369     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16370       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16371     } else {
16372       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16373               WhiteOnMove(moveNumber) ? " " : ".. ",
16374               parseList[moveNumber]);
16375     }
16376     if (text != NULL && (appData.autoDisplayComment || commentUp))
16377         CommentPopUp(title, text);
16378 }
16379
16380 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16381  * might be busy thinking or pondering.  It can be omitted if your
16382  * gnuchess is configured to stop thinking immediately on any user
16383  * input.  However, that gnuchess feature depends on the FIONREAD
16384  * ioctl, which does not work properly on some flavors of Unix.
16385  */
16386 void
16387 Attention (ChessProgramState *cps)
16388 {
16389 #if ATTENTION
16390     if (!cps->useSigint) return;
16391     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16392     switch (gameMode) {
16393       case MachinePlaysWhite:
16394       case MachinePlaysBlack:
16395       case TwoMachinesPlay:
16396       case IcsPlayingWhite:
16397       case IcsPlayingBlack:
16398       case AnalyzeMode:
16399       case AnalyzeFile:
16400         /* Skip if we know it isn't thinking */
16401         if (!cps->maybeThinking) return;
16402         if (appData.debugMode)
16403           fprintf(debugFP, "Interrupting %s\n", cps->which);
16404         InterruptChildProcess(cps->pr);
16405         cps->maybeThinking = FALSE;
16406         break;
16407       default:
16408         break;
16409     }
16410 #endif /*ATTENTION*/
16411 }
16412
16413 int
16414 CheckFlags ()
16415 {
16416     if (whiteTimeRemaining <= 0) {
16417         if (!whiteFlag) {
16418             whiteFlag = TRUE;
16419             if (appData.icsActive) {
16420                 if (appData.autoCallFlag &&
16421                     gameMode == IcsPlayingBlack && !blackFlag) {
16422                   SendToICS(ics_prefix);
16423                   SendToICS("flag\n");
16424                 }
16425             } else {
16426                 if (blackFlag) {
16427                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16428                 } else {
16429                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16430                     if (appData.autoCallFlag) {
16431                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16432                         return TRUE;
16433                     }
16434                 }
16435             }
16436         }
16437     }
16438     if (blackTimeRemaining <= 0) {
16439         if (!blackFlag) {
16440             blackFlag = TRUE;
16441             if (appData.icsActive) {
16442                 if (appData.autoCallFlag &&
16443                     gameMode == IcsPlayingWhite && !whiteFlag) {
16444                   SendToICS(ics_prefix);
16445                   SendToICS("flag\n");
16446                 }
16447             } else {
16448                 if (whiteFlag) {
16449                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16450                 } else {
16451                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16452                     if (appData.autoCallFlag) {
16453                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16454                         return TRUE;
16455                     }
16456                 }
16457             }
16458         }
16459     }
16460     return FALSE;
16461 }
16462
16463 void
16464 CheckTimeControl ()
16465 {
16466     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16467         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16468
16469     /*
16470      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16471      */
16472     if ( !WhiteOnMove(forwardMostMove) ) {
16473         /* White made time control */
16474         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16475         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16476         /* [HGM] time odds: correct new time quota for time odds! */
16477                                             / WhitePlayer()->timeOdds;
16478         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16479     } else {
16480         lastBlack -= blackTimeRemaining;
16481         /* Black made time control */
16482         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16483                                             / WhitePlayer()->other->timeOdds;
16484         lastWhite = whiteTimeRemaining;
16485     }
16486 }
16487
16488 void
16489 DisplayBothClocks ()
16490 {
16491     int wom = gameMode == EditPosition ?
16492       !blackPlaysFirst : WhiteOnMove(currentMove);
16493     DisplayWhiteClock(whiteTimeRemaining, wom);
16494     DisplayBlackClock(blackTimeRemaining, !wom);
16495 }
16496
16497
16498 /* Timekeeping seems to be a portability nightmare.  I think everyone
16499    has ftime(), but I'm really not sure, so I'm including some ifdefs
16500    to use other calls if you don't.  Clocks will be less accurate if
16501    you have neither ftime nor gettimeofday.
16502 */
16503
16504 /* VS 2008 requires the #include outside of the function */
16505 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16506 #include <sys/timeb.h>
16507 #endif
16508
16509 /* Get the current time as a TimeMark */
16510 void
16511 GetTimeMark (TimeMark *tm)
16512 {
16513 #if HAVE_GETTIMEOFDAY
16514
16515     struct timeval timeVal;
16516     struct timezone timeZone;
16517
16518     gettimeofday(&timeVal, &timeZone);
16519     tm->sec = (long) timeVal.tv_sec;
16520     tm->ms = (int) (timeVal.tv_usec / 1000L);
16521
16522 #else /*!HAVE_GETTIMEOFDAY*/
16523 #if HAVE_FTIME
16524
16525 // include <sys/timeb.h> / moved to just above start of function
16526     struct timeb timeB;
16527
16528     ftime(&timeB);
16529     tm->sec = (long) timeB.time;
16530     tm->ms = (int) timeB.millitm;
16531
16532 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16533     tm->sec = (long) time(NULL);
16534     tm->ms = 0;
16535 #endif
16536 #endif
16537 }
16538
16539 /* Return the difference in milliseconds between two
16540    time marks.  We assume the difference will fit in a long!
16541 */
16542 long
16543 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16544 {
16545     return 1000L*(tm2->sec - tm1->sec) +
16546            (long) (tm2->ms - tm1->ms);
16547 }
16548
16549
16550 /*
16551  * Code to manage the game clocks.
16552  *
16553  * In tournament play, black starts the clock and then white makes a move.
16554  * We give the human user a slight advantage if he is playing white---the
16555  * clocks don't run until he makes his first move, so it takes zero time.
16556  * Also, we don't account for network lag, so we could get out of sync
16557  * with GNU Chess's clock -- but then, referees are always right.
16558  */
16559
16560 static TimeMark tickStartTM;
16561 static long intendedTickLength;
16562
16563 long
16564 NextTickLength (long timeRemaining)
16565 {
16566     long nominalTickLength, nextTickLength;
16567
16568     if (timeRemaining > 0L && timeRemaining <= 10000L)
16569       nominalTickLength = 100L;
16570     else
16571       nominalTickLength = 1000L;
16572     nextTickLength = timeRemaining % nominalTickLength;
16573     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16574
16575     return nextTickLength;
16576 }
16577
16578 /* Adjust clock one minute up or down */
16579 void
16580 AdjustClock (Boolean which, int dir)
16581 {
16582     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16583     if(which) blackTimeRemaining += 60000*dir;
16584     else      whiteTimeRemaining += 60000*dir;
16585     DisplayBothClocks();
16586     adjustedClock = TRUE;
16587 }
16588
16589 /* Stop clocks and reset to a fresh time control */
16590 void
16591 ResetClocks ()
16592 {
16593     (void) StopClockTimer();
16594     if (appData.icsActive) {
16595         whiteTimeRemaining = blackTimeRemaining = 0;
16596     } else if (searchTime) {
16597         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16598         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16599     } else { /* [HGM] correct new time quote for time odds */
16600         whiteTC = blackTC = fullTimeControlString;
16601         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16602         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16603     }
16604     if (whiteFlag || blackFlag) {
16605         DisplayTitle("");
16606         whiteFlag = blackFlag = FALSE;
16607     }
16608     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16609     DisplayBothClocks();
16610     adjustedClock = FALSE;
16611 }
16612
16613 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16614
16615 /* Decrement running clock by amount of time that has passed */
16616 void
16617 DecrementClocks ()
16618 {
16619     long timeRemaining;
16620     long lastTickLength, fudge;
16621     TimeMark now;
16622
16623     if (!appData.clockMode) return;
16624     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16625
16626     GetTimeMark(&now);
16627
16628     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16629
16630     /* Fudge if we woke up a little too soon */
16631     fudge = intendedTickLength - lastTickLength;
16632     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16633
16634     if (WhiteOnMove(forwardMostMove)) {
16635         if(whiteNPS >= 0) lastTickLength = 0;
16636         timeRemaining = whiteTimeRemaining -= lastTickLength;
16637         if(timeRemaining < 0 && !appData.icsActive) {
16638             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16639             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16640                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16641                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16642             }
16643         }
16644         DisplayWhiteClock(whiteTimeRemaining - fudge,
16645                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16646     } else {
16647         if(blackNPS >= 0) lastTickLength = 0;
16648         timeRemaining = blackTimeRemaining -= lastTickLength;
16649         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16650             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16651             if(suddenDeath) {
16652                 blackStartMove = forwardMostMove;
16653                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16654             }
16655         }
16656         DisplayBlackClock(blackTimeRemaining - fudge,
16657                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16658     }
16659     if (CheckFlags()) return;
16660
16661     if(twoBoards) { // count down secondary board's clocks as well
16662         activePartnerTime -= lastTickLength;
16663         partnerUp = 1;
16664         if(activePartner == 'W')
16665             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16666         else
16667             DisplayBlackClock(activePartnerTime, TRUE);
16668         partnerUp = 0;
16669     }
16670
16671     tickStartTM = now;
16672     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16673     StartClockTimer(intendedTickLength);
16674
16675     /* if the time remaining has fallen below the alarm threshold, sound the
16676      * alarm. if the alarm has sounded and (due to a takeback or time control
16677      * with increment) the time remaining has increased to a level above the
16678      * threshold, reset the alarm so it can sound again.
16679      */
16680
16681     if (appData.icsActive && appData.icsAlarm) {
16682
16683         /* make sure we are dealing with the user's clock */
16684         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16685                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16686            )) return;
16687
16688         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16689             alarmSounded = FALSE;
16690         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16691             PlayAlarmSound();
16692             alarmSounded = TRUE;
16693         }
16694     }
16695 }
16696
16697
16698 /* A player has just moved, so stop the previously running
16699    clock and (if in clock mode) start the other one.
16700    We redisplay both clocks in case we're in ICS mode, because
16701    ICS gives us an update to both clocks after every move.
16702    Note that this routine is called *after* forwardMostMove
16703    is updated, so the last fractional tick must be subtracted
16704    from the color that is *not* on move now.
16705 */
16706 void
16707 SwitchClocks (int newMoveNr)
16708 {
16709     long lastTickLength;
16710     TimeMark now;
16711     int flagged = FALSE;
16712
16713     GetTimeMark(&now);
16714
16715     if (StopClockTimer() && appData.clockMode) {
16716         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16717         if (!WhiteOnMove(forwardMostMove)) {
16718             if(blackNPS >= 0) lastTickLength = 0;
16719             blackTimeRemaining -= lastTickLength;
16720            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16721 //         if(pvInfoList[forwardMostMove].time == -1)
16722                  pvInfoList[forwardMostMove].time =               // use GUI time
16723                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16724         } else {
16725            if(whiteNPS >= 0) lastTickLength = 0;
16726            whiteTimeRemaining -= lastTickLength;
16727            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16728 //         if(pvInfoList[forwardMostMove].time == -1)
16729                  pvInfoList[forwardMostMove].time =
16730                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16731         }
16732         flagged = CheckFlags();
16733     }
16734     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16735     CheckTimeControl();
16736
16737     if (flagged || !appData.clockMode) return;
16738
16739     switch (gameMode) {
16740       case MachinePlaysBlack:
16741       case MachinePlaysWhite:
16742       case BeginningOfGame:
16743         if (pausing) return;
16744         break;
16745
16746       case EditGame:
16747       case PlayFromGameFile:
16748       case IcsExamining:
16749         return;
16750
16751       default:
16752         break;
16753     }
16754
16755     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16756         if(WhiteOnMove(forwardMostMove))
16757              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16758         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16759     }
16760
16761     tickStartTM = now;
16762     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16763       whiteTimeRemaining : blackTimeRemaining);
16764     StartClockTimer(intendedTickLength);
16765 }
16766
16767
16768 /* Stop both clocks */
16769 void
16770 StopClocks ()
16771 {
16772     long lastTickLength;
16773     TimeMark now;
16774
16775     if (!StopClockTimer()) return;
16776     if (!appData.clockMode) return;
16777
16778     GetTimeMark(&now);
16779
16780     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16781     if (WhiteOnMove(forwardMostMove)) {
16782         if(whiteNPS >= 0) lastTickLength = 0;
16783         whiteTimeRemaining -= lastTickLength;
16784         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16785     } else {
16786         if(blackNPS >= 0) lastTickLength = 0;
16787         blackTimeRemaining -= lastTickLength;
16788         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16789     }
16790     CheckFlags();
16791 }
16792
16793 /* Start clock of player on move.  Time may have been reset, so
16794    if clock is already running, stop and restart it. */
16795 void
16796 StartClocks ()
16797 {
16798     (void) StopClockTimer(); /* in case it was running already */
16799     DisplayBothClocks();
16800     if (CheckFlags()) return;
16801
16802     if (!appData.clockMode) return;
16803     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16804
16805     GetTimeMark(&tickStartTM);
16806     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16807       whiteTimeRemaining : blackTimeRemaining);
16808
16809    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16810     whiteNPS = blackNPS = -1;
16811     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16812        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16813         whiteNPS = first.nps;
16814     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16815        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16816         blackNPS = first.nps;
16817     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16818         whiteNPS = second.nps;
16819     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16820         blackNPS = second.nps;
16821     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16822
16823     StartClockTimer(intendedTickLength);
16824 }
16825
16826 char *
16827 TimeString (long ms)
16828 {
16829     long second, minute, hour, day;
16830     char *sign = "";
16831     static char buf[32];
16832
16833     if (ms > 0 && ms <= 9900) {
16834       /* convert milliseconds to tenths, rounding up */
16835       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16836
16837       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16838       return buf;
16839     }
16840
16841     /* convert milliseconds to seconds, rounding up */
16842     /* use floating point to avoid strangeness of integer division
16843        with negative dividends on many machines */
16844     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16845
16846     if (second < 0) {
16847         sign = "-";
16848         second = -second;
16849     }
16850
16851     day = second / (60 * 60 * 24);
16852     second = second % (60 * 60 * 24);
16853     hour = second / (60 * 60);
16854     second = second % (60 * 60);
16855     minute = second / 60;
16856     second = second % 60;
16857
16858     if (day > 0)
16859       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16860               sign, day, hour, minute, second);
16861     else if (hour > 0)
16862       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16863     else
16864       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16865
16866     return buf;
16867 }
16868
16869
16870 /*
16871  * This is necessary because some C libraries aren't ANSI C compliant yet.
16872  */
16873 char *
16874 StrStr (char *string, char *match)
16875 {
16876     int i, length;
16877
16878     length = strlen(match);
16879
16880     for (i = strlen(string) - length; i >= 0; i--, string++)
16881       if (!strncmp(match, string, length))
16882         return string;
16883
16884     return NULL;
16885 }
16886
16887 char *
16888 StrCaseStr (char *string, char *match)
16889 {
16890     int i, j, length;
16891
16892     length = strlen(match);
16893
16894     for (i = strlen(string) - length; i >= 0; i--, string++) {
16895         for (j = 0; j < length; j++) {
16896             if (ToLower(match[j]) != ToLower(string[j]))
16897               break;
16898         }
16899         if (j == length) return string;
16900     }
16901
16902     return NULL;
16903 }
16904
16905 #ifndef _amigados
16906 int
16907 StrCaseCmp (char *s1, char *s2)
16908 {
16909     char c1, c2;
16910
16911     for (;;) {
16912         c1 = ToLower(*s1++);
16913         c2 = ToLower(*s2++);
16914         if (c1 > c2) return 1;
16915         if (c1 < c2) return -1;
16916         if (c1 == NULLCHAR) return 0;
16917     }
16918 }
16919
16920
16921 int
16922 ToLower (int c)
16923 {
16924     return isupper(c) ? tolower(c) : c;
16925 }
16926
16927
16928 int
16929 ToUpper (int c)
16930 {
16931     return islower(c) ? toupper(c) : c;
16932 }
16933 #endif /* !_amigados    */
16934
16935 char *
16936 StrSave (char *s)
16937 {
16938   char *ret;
16939
16940   if ((ret = (char *) malloc(strlen(s) + 1)))
16941     {
16942       safeStrCpy(ret, s, strlen(s)+1);
16943     }
16944   return ret;
16945 }
16946
16947 char *
16948 StrSavePtr (char *s, char **savePtr)
16949 {
16950     if (*savePtr) {
16951         free(*savePtr);
16952     }
16953     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16954       safeStrCpy(*savePtr, s, strlen(s)+1);
16955     }
16956     return(*savePtr);
16957 }
16958
16959 char *
16960 PGNDate ()
16961 {
16962     time_t clock;
16963     struct tm *tm;
16964     char buf[MSG_SIZ];
16965
16966     clock = time((time_t *)NULL);
16967     tm = localtime(&clock);
16968     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16969             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16970     return StrSave(buf);
16971 }
16972
16973
16974 char *
16975 PositionToFEN (int move, char *overrideCastling)
16976 {
16977     int i, j, fromX, fromY, toX, toY;
16978     int whiteToPlay;
16979     char buf[MSG_SIZ];
16980     char *p, *q;
16981     int emptycount;
16982     ChessSquare piece;
16983
16984     whiteToPlay = (gameMode == EditPosition) ?
16985       !blackPlaysFirst : (move % 2 == 0);
16986     p = buf;
16987
16988     /* Piece placement data */
16989     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16990         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16991         emptycount = 0;
16992         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16993             if (boards[move][i][j] == EmptySquare) {
16994                 emptycount++;
16995             } else { ChessSquare piece = boards[move][i][j];
16996                 if (emptycount > 0) {
16997                     if(emptycount<10) /* [HGM] can be >= 10 */
16998                         *p++ = '0' + emptycount;
16999                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17000                     emptycount = 0;
17001                 }
17002                 if(PieceToChar(piece) == '+') {
17003                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17004                     *p++ = '+';
17005                     piece = (ChessSquare)(DEMOTED piece);
17006                 }
17007                 *p++ = PieceToChar(piece);
17008                 if(p[-1] == '~') {
17009                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17010                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17011                     *p++ = '~';
17012                 }
17013             }
17014         }
17015         if (emptycount > 0) {
17016             if(emptycount<10) /* [HGM] can be >= 10 */
17017                 *p++ = '0' + emptycount;
17018             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17019             emptycount = 0;
17020         }
17021         *p++ = '/';
17022     }
17023     *(p - 1) = ' ';
17024
17025     /* [HGM] print Crazyhouse or Shogi holdings */
17026     if( gameInfo.holdingsWidth ) {
17027         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17028         q = p;
17029         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17030             piece = boards[move][i][BOARD_WIDTH-1];
17031             if( piece != EmptySquare )
17032               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17033                   *p++ = PieceToChar(piece);
17034         }
17035         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17036             piece = boards[move][BOARD_HEIGHT-i-1][0];
17037             if( piece != EmptySquare )
17038               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17039                   *p++ = PieceToChar(piece);
17040         }
17041
17042         if( q == p ) *p++ = '-';
17043         *p++ = ']';
17044         *p++ = ' ';
17045     }
17046
17047     /* Active color */
17048     *p++ = whiteToPlay ? 'w' : 'b';
17049     *p++ = ' ';
17050
17051   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17052     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17053   } else {
17054   if(nrCastlingRights) {
17055      q = p;
17056      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17057        /* [HGM] write directly from rights */
17058            if(boards[move][CASTLING][2] != NoRights &&
17059               boards[move][CASTLING][0] != NoRights   )
17060                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17061            if(boards[move][CASTLING][2] != NoRights &&
17062               boards[move][CASTLING][1] != NoRights   )
17063                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17064            if(boards[move][CASTLING][5] != NoRights &&
17065               boards[move][CASTLING][3] != NoRights   )
17066                 *p++ = boards[move][CASTLING][3] + AAA;
17067            if(boards[move][CASTLING][5] != NoRights &&
17068               boards[move][CASTLING][4] != NoRights   )
17069                 *p++ = boards[move][CASTLING][4] + AAA;
17070      } else {
17071
17072         /* [HGM] write true castling rights */
17073         if( nrCastlingRights == 6 ) {
17074             int q, k=0;
17075             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17076                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17077             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17078                  boards[move][CASTLING][2] != NoRights  );
17079             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17080                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17081                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17082                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17083                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17084             }
17085             if(q) *p++ = 'Q';
17086             k = 0;
17087             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17088                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17089             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17090                  boards[move][CASTLING][5] != NoRights  );
17091             if(gameInfo.variant == VariantSChess) {
17092                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17093                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17094                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17095                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17096             }
17097             if(q) *p++ = 'q';
17098         }
17099      }
17100      if (q == p) *p++ = '-'; /* No castling rights */
17101      *p++ = ' ';
17102   }
17103
17104   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17105      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17106     /* En passant target square */
17107     if (move > backwardMostMove) {
17108         fromX = moveList[move - 1][0] - AAA;
17109         fromY = moveList[move - 1][1] - ONE;
17110         toX = moveList[move - 1][2] - AAA;
17111         toY = moveList[move - 1][3] - ONE;
17112         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17113             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17114             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17115             fromX == toX) {
17116             /* 2-square pawn move just happened */
17117             *p++ = toX + AAA;
17118             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17119         } else {
17120             *p++ = '-';
17121         }
17122     } else if(move == backwardMostMove) {
17123         // [HGM] perhaps we should always do it like this, and forget the above?
17124         if((signed char)boards[move][EP_STATUS] >= 0) {
17125             *p++ = boards[move][EP_STATUS] + AAA;
17126             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17127         } else {
17128             *p++ = '-';
17129         }
17130     } else {
17131         *p++ = '-';
17132     }
17133     *p++ = ' ';
17134   }
17135   }
17136
17137     /* [HGM] find reversible plies */
17138     {   int i = 0, j=move;
17139
17140         if (appData.debugMode) { int k;
17141             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17142             for(k=backwardMostMove; k<=forwardMostMove; k++)
17143                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17144
17145         }
17146
17147         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17148         if( j == backwardMostMove ) i += initialRulePlies;
17149         sprintf(p, "%d ", i);
17150         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17151     }
17152     /* Fullmove number */
17153     sprintf(p, "%d", (move / 2) + 1);
17154
17155     return StrSave(buf);
17156 }
17157
17158 Boolean
17159 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17160 {
17161     int i, j;
17162     char *p, c;
17163     int emptycount, virgin[BOARD_FILES];
17164     ChessSquare piece;
17165
17166     p = fen;
17167
17168     /* [HGM] by default clear Crazyhouse holdings, if present */
17169     if(gameInfo.holdingsWidth) {
17170        for(i=0; i<BOARD_HEIGHT; i++) {
17171            board[i][0]             = EmptySquare; /* black holdings */
17172            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17173            board[i][1]             = (ChessSquare) 0; /* black counts */
17174            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17175        }
17176     }
17177
17178     /* Piece placement data */
17179     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17180         j = 0;
17181         for (;;) {
17182             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17183                 if (*p == '/') p++;
17184                 emptycount = gameInfo.boardWidth - j;
17185                 while (emptycount--)
17186                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17187                 break;
17188 #if(BOARD_FILES >= 10)
17189             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17190                 p++; emptycount=10;
17191                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17192                 while (emptycount--)
17193                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17194 #endif
17195             } else if (isdigit(*p)) {
17196                 emptycount = *p++ - '0';
17197                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17198                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17199                 while (emptycount--)
17200                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17201             } else if (*p == '+' || isalpha(*p)) {
17202                 if (j >= gameInfo.boardWidth) return FALSE;
17203                 if(*p=='+') {
17204                     piece = CharToPiece(*++p);
17205                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17206                     piece = (ChessSquare) (PROMOTED piece ); p++;
17207                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17208                 } else piece = CharToPiece(*p++);
17209
17210                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17211                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17212                     piece = (ChessSquare) (PROMOTED piece);
17213                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17214                     p++;
17215                 }
17216                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17217             } else {
17218                 return FALSE;
17219             }
17220         }
17221     }
17222     while (*p == '/' || *p == ' ') p++;
17223
17224     /* [HGM] look for Crazyhouse holdings here */
17225     while(*p==' ') p++;
17226     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17227         if(*p == '[') p++;
17228         if(*p == '-' ) p++; /* empty holdings */ else {
17229             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17230             /* if we would allow FEN reading to set board size, we would   */
17231             /* have to add holdings and shift the board read so far here   */
17232             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17233                 p++;
17234                 if((int) piece >= (int) BlackPawn ) {
17235                     i = (int)piece - (int)BlackPawn;
17236                     i = PieceToNumber((ChessSquare)i);
17237                     if( i >= gameInfo.holdingsSize ) return FALSE;
17238                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17239                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17240                 } else {
17241                     i = (int)piece - (int)WhitePawn;
17242                     i = PieceToNumber((ChessSquare)i);
17243                     if( i >= gameInfo.holdingsSize ) return FALSE;
17244                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17245                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17246                 }
17247             }
17248         }
17249         if(*p == ']') p++;
17250     }
17251
17252     while(*p == ' ') p++;
17253
17254     /* Active color */
17255     c = *p++;
17256     if(appData.colorNickNames) {
17257       if( c == appData.colorNickNames[0] ) c = 'w'; else
17258       if( c == appData.colorNickNames[1] ) c = 'b';
17259     }
17260     switch (c) {
17261       case 'w':
17262         *blackPlaysFirst = FALSE;
17263         break;
17264       case 'b':
17265         *blackPlaysFirst = TRUE;
17266         break;
17267       default:
17268         return FALSE;
17269     }
17270
17271     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17272     /* return the extra info in global variiables             */
17273
17274     /* set defaults in case FEN is incomplete */
17275     board[EP_STATUS] = EP_UNKNOWN;
17276     for(i=0; i<nrCastlingRights; i++ ) {
17277         board[CASTLING][i] =
17278             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17279     }   /* assume possible unless obviously impossible */
17280     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17281     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17282     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17283                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17284     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17285     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17286     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17287                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17288     FENrulePlies = 0;
17289
17290     while(*p==' ') p++;
17291     if(nrCastlingRights) {
17292       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17293       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17294           /* castling indicator present, so default becomes no castlings */
17295           for(i=0; i<nrCastlingRights; i++ ) {
17296                  board[CASTLING][i] = NoRights;
17297           }
17298       }
17299       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17300              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17301              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17302              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17303         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17304
17305         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17306             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17307             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17308         }
17309         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17310             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17311         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17312                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17313         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17314                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17315         switch(c) {
17316           case'K':
17317               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17318               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17319               board[CASTLING][2] = whiteKingFile;
17320               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17321               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17322               break;
17323           case'Q':
17324               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17325               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17326               board[CASTLING][2] = whiteKingFile;
17327               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17328               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17329               break;
17330           case'k':
17331               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17332               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17333               board[CASTLING][5] = blackKingFile;
17334               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17335               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17336               break;
17337           case'q':
17338               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17339               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17340               board[CASTLING][5] = blackKingFile;
17341               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17342               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17343           case '-':
17344               break;
17345           default: /* FRC castlings */
17346               if(c >= 'a') { /* black rights */
17347                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17348                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17349                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17350                   if(i == BOARD_RGHT) break;
17351                   board[CASTLING][5] = i;
17352                   c -= AAA;
17353                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17354                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17355                   if(c > i)
17356                       board[CASTLING][3] = c;
17357                   else
17358                       board[CASTLING][4] = c;
17359               } else { /* white rights */
17360                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17361                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17362                     if(board[0][i] == WhiteKing) break;
17363                   if(i == BOARD_RGHT) break;
17364                   board[CASTLING][2] = i;
17365                   c -= AAA - 'a' + 'A';
17366                   if(board[0][c] >= WhiteKing) break;
17367                   if(c > i)
17368                       board[CASTLING][0] = c;
17369                   else
17370                       board[CASTLING][1] = c;
17371               }
17372         }
17373       }
17374       for(i=0; i<nrCastlingRights; i++)
17375         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17376       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17377     if (appData.debugMode) {
17378         fprintf(debugFP, "FEN castling rights:");
17379         for(i=0; i<nrCastlingRights; i++)
17380         fprintf(debugFP, " %d", board[CASTLING][i]);
17381         fprintf(debugFP, "\n");
17382     }
17383
17384       while(*p==' ') p++;
17385     }
17386
17387     /* read e.p. field in games that know e.p. capture */
17388     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17389        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17390       if(*p=='-') {
17391         p++; board[EP_STATUS] = EP_NONE;
17392       } else {
17393          char c = *p++ - AAA;
17394
17395          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17396          if(*p >= '0' && *p <='9') p++;
17397          board[EP_STATUS] = c;
17398       }
17399     }
17400
17401
17402     if(sscanf(p, "%d", &i) == 1) {
17403         FENrulePlies = i; /* 50-move ply counter */
17404         /* (The move number is still ignored)    */
17405     }
17406
17407     return TRUE;
17408 }
17409
17410 void
17411 EditPositionPasteFEN (char *fen)
17412 {
17413   if (fen != NULL) {
17414     Board initial_position;
17415
17416     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17417       DisplayError(_("Bad FEN position in clipboard"), 0);
17418       return ;
17419     } else {
17420       int savedBlackPlaysFirst = blackPlaysFirst;
17421       EditPositionEvent();
17422       blackPlaysFirst = savedBlackPlaysFirst;
17423       CopyBoard(boards[0], initial_position);
17424       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17425       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17426       DisplayBothClocks();
17427       DrawPosition(FALSE, boards[currentMove]);
17428     }
17429   }
17430 }
17431
17432 static char cseq[12] = "\\   ";
17433
17434 Boolean
17435 set_cont_sequence (char *new_seq)
17436 {
17437     int len;
17438     Boolean ret;
17439
17440     // handle bad attempts to set the sequence
17441         if (!new_seq)
17442                 return 0; // acceptable error - no debug
17443
17444     len = strlen(new_seq);
17445     ret = (len > 0) && (len < sizeof(cseq));
17446     if (ret)
17447       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17448     else if (appData.debugMode)
17449       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17450     return ret;
17451 }
17452
17453 /*
17454     reformat a source message so words don't cross the width boundary.  internal
17455     newlines are not removed.  returns the wrapped size (no null character unless
17456     included in source message).  If dest is NULL, only calculate the size required
17457     for the dest buffer.  lp argument indicats line position upon entry, and it's
17458     passed back upon exit.
17459 */
17460 int
17461 wrap (char *dest, char *src, int count, int width, int *lp)
17462 {
17463     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17464
17465     cseq_len = strlen(cseq);
17466     old_line = line = *lp;
17467     ansi = len = clen = 0;
17468
17469     for (i=0; i < count; i++)
17470     {
17471         if (src[i] == '\033')
17472             ansi = 1;
17473
17474         // if we hit the width, back up
17475         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17476         {
17477             // store i & len in case the word is too long
17478             old_i = i, old_len = len;
17479
17480             // find the end of the last word
17481             while (i && src[i] != ' ' && src[i] != '\n')
17482             {
17483                 i--;
17484                 len--;
17485             }
17486
17487             // word too long?  restore i & len before splitting it
17488             if ((old_i-i+clen) >= width)
17489             {
17490                 i = old_i;
17491                 len = old_len;
17492             }
17493
17494             // extra space?
17495             if (i && src[i-1] == ' ')
17496                 len--;
17497
17498             if (src[i] != ' ' && src[i] != '\n')
17499             {
17500                 i--;
17501                 if (len)
17502                     len--;
17503             }
17504
17505             // now append the newline and continuation sequence
17506             if (dest)
17507                 dest[len] = '\n';
17508             len++;
17509             if (dest)
17510                 strncpy(dest+len, cseq, cseq_len);
17511             len += cseq_len;
17512             line = cseq_len;
17513             clen = cseq_len;
17514             continue;
17515         }
17516
17517         if (dest)
17518             dest[len] = src[i];
17519         len++;
17520         if (!ansi)
17521             line++;
17522         if (src[i] == '\n')
17523             line = 0;
17524         if (src[i] == 'm')
17525             ansi = 0;
17526     }
17527     if (dest && appData.debugMode)
17528     {
17529         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17530             count, width, line, len, *lp);
17531         show_bytes(debugFP, src, count);
17532         fprintf(debugFP, "\ndest: ");
17533         show_bytes(debugFP, dest, len);
17534         fprintf(debugFP, "\n");
17535     }
17536     *lp = dest ? line : old_line;
17537
17538     return len;
17539 }
17540
17541 // [HGM] vari: routines for shelving variations
17542 Boolean modeRestore = FALSE;
17543
17544 void
17545 PushInner (int firstMove, int lastMove)
17546 {
17547         int i, j, nrMoves = lastMove - firstMove;
17548
17549         // push current tail of game on stack
17550         savedResult[storedGames] = gameInfo.result;
17551         savedDetails[storedGames] = gameInfo.resultDetails;
17552         gameInfo.resultDetails = NULL;
17553         savedFirst[storedGames] = firstMove;
17554         savedLast [storedGames] = lastMove;
17555         savedFramePtr[storedGames] = framePtr;
17556         framePtr -= nrMoves; // reserve space for the boards
17557         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17558             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17559             for(j=0; j<MOVE_LEN; j++)
17560                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17561             for(j=0; j<2*MOVE_LEN; j++)
17562                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17563             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17564             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17565             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17566             pvInfoList[firstMove+i-1].depth = 0;
17567             commentList[framePtr+i] = commentList[firstMove+i];
17568             commentList[firstMove+i] = NULL;
17569         }
17570
17571         storedGames++;
17572         forwardMostMove = firstMove; // truncate game so we can start variation
17573 }
17574
17575 void
17576 PushTail (int firstMove, int lastMove)
17577 {
17578         if(appData.icsActive) { // only in local mode
17579                 forwardMostMove = currentMove; // mimic old ICS behavior
17580                 return;
17581         }
17582         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17583
17584         PushInner(firstMove, lastMove);
17585         if(storedGames == 1) GreyRevert(FALSE);
17586         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17587 }
17588
17589 void
17590 PopInner (Boolean annotate)
17591 {
17592         int i, j, nrMoves;
17593         char buf[8000], moveBuf[20];
17594
17595         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17596         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17597         nrMoves = savedLast[storedGames] - currentMove;
17598         if(annotate) {
17599                 int cnt = 10;
17600                 if(!WhiteOnMove(currentMove))
17601                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17602                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17603                 for(i=currentMove; i<forwardMostMove; i++) {
17604                         if(WhiteOnMove(i))
17605                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17606                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17607                         strcat(buf, moveBuf);
17608                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17609                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17610                 }
17611                 strcat(buf, ")");
17612         }
17613         for(i=1; i<=nrMoves; i++) { // copy last variation back
17614             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17615             for(j=0; j<MOVE_LEN; j++)
17616                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17617             for(j=0; j<2*MOVE_LEN; j++)
17618                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17619             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17620             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17621             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17622             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17623             commentList[currentMove+i] = commentList[framePtr+i];
17624             commentList[framePtr+i] = NULL;
17625         }
17626         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17627         framePtr = savedFramePtr[storedGames];
17628         gameInfo.result = savedResult[storedGames];
17629         if(gameInfo.resultDetails != NULL) {
17630             free(gameInfo.resultDetails);
17631       }
17632         gameInfo.resultDetails = savedDetails[storedGames];
17633         forwardMostMove = currentMove + nrMoves;
17634 }
17635
17636 Boolean
17637 PopTail (Boolean annotate)
17638 {
17639         if(appData.icsActive) return FALSE; // only in local mode
17640         if(!storedGames) return FALSE; // sanity
17641         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17642
17643         PopInner(annotate);
17644         if(currentMove < forwardMostMove) ForwardEvent(); else
17645         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17646
17647         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17648         return TRUE;
17649 }
17650
17651 void
17652 CleanupTail ()
17653 {       // remove all shelved variations
17654         int i;
17655         for(i=0; i<storedGames; i++) {
17656             if(savedDetails[i])
17657                 free(savedDetails[i]);
17658             savedDetails[i] = NULL;
17659         }
17660         for(i=framePtr; i<MAX_MOVES; i++) {
17661                 if(commentList[i]) free(commentList[i]);
17662                 commentList[i] = NULL;
17663         }
17664         framePtr = MAX_MOVES-1;
17665         storedGames = 0;
17666 }
17667
17668 void
17669 LoadVariation (int index, char *text)
17670 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17671         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17672         int level = 0, move;
17673
17674         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17675         // first find outermost bracketing variation
17676         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17677             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17678                 if(*p == '{') wait = '}'; else
17679                 if(*p == '[') wait = ']'; else
17680                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17681                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17682             }
17683             if(*p == wait) wait = NULLCHAR; // closing ]} found
17684             p++;
17685         }
17686         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17687         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17688         end[1] = NULLCHAR; // clip off comment beyond variation
17689         ToNrEvent(currentMove-1);
17690         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17691         // kludge: use ParsePV() to append variation to game
17692         move = currentMove;
17693         ParsePV(start, TRUE, TRUE);
17694         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17695         ClearPremoveHighlights();
17696         CommentPopDown();
17697         ToNrEvent(currentMove+1);
17698 }
17699
17700 void
17701 LoadTheme ()
17702 {
17703     char *p, *q, buf[MSG_SIZ];
17704     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17705         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17706         ParseArgsFromString(buf);
17707         ActivateTheme(TRUE); // also redo colors
17708         return;
17709     }
17710     p = nickName;
17711     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17712     {
17713         int len;
17714         q = appData.themeNames;
17715         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17716       if(appData.useBitmaps) {
17717         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17718                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17719                 appData.liteBackTextureMode,
17720                 appData.darkBackTextureMode );
17721       } else {
17722         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17723                 Col2Text(2),   // lightSquareColor
17724                 Col2Text(3) ); // darkSquareColor
17725       }
17726       if(appData.useBorder) {
17727         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17728                 appData.border);
17729       } else {
17730         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17731       }
17732       if(appData.useFont) {
17733         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17734                 appData.renderPiecesWithFont,
17735                 appData.fontToPieceTable,
17736                 Col2Text(9),    // appData.fontBackColorWhite
17737                 Col2Text(10) ); // appData.fontForeColorBlack
17738       } else {
17739         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17740                 appData.pieceDirectory);
17741         if(!appData.pieceDirectory[0])
17742           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17743                 Col2Text(0),   // whitePieceColor
17744                 Col2Text(1) ); // blackPieceColor
17745       }
17746       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17747                 Col2Text(4),   // highlightSquareColor
17748                 Col2Text(5) ); // premoveHighlightColor
17749         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17750         if(insert != q) insert[-1] = NULLCHAR;
17751         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17752         if(q)   free(q);
17753     }
17754     ActivateTheme(FALSE);
17755 }