67e2d1ba1d34c61b18f4e235dacc2478600f9032
[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;
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 void
844 LoadEngine ()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine (ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] =
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
883         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
884         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
885
886 void
887 FloatToFront(char **list, char *engineLine)
888 {
889     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
890     int i=0;
891     if(appData.recentEngines <= 0) return;
892     TidyProgramName(engineLine, "localhost", tidy+1);
893     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
894     strncpy(buf+1, *list, MSG_SIZ-50);
895     if(p = strstr(buf, tidy)) { // tidy name appears in list
896         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
897         while(*p++ = *++q); // squeeze out
898     }
899     strcat(tidy, buf+1); // put list behind tidy name
900     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
901     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
902     ASSIGN(*list, tidy+1);
903 }
904
905 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
906
907 void
908 Load (ChessProgramState *cps, int i)
909 {
910     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
911     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
912         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
913         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
914         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
915         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
916         appData.firstProtocolVersion = PROTOVER;
917         ParseArgsFromString(buf);
918         SwapEngines(i);
919         ReplaceEngine(cps, i);
920         FloatToFront(&appData.recentEngineList, engineLine);
921         return;
922     }
923     p = engineName;
924     while(q = strchr(p, SLASH)) p = q+1;
925     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
926     if(engineDir[0] != NULLCHAR) {
927         ASSIGN(appData.directory[i], engineDir); p = engineName;
928     } else if(p != engineName) { // derive directory from engine path, when not given
929         p[-1] = 0;
930         ASSIGN(appData.directory[i], engineName);
931         p[-1] = SLASH;
932         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
933     } else { ASSIGN(appData.directory[i], "."); }
934     if(params[0]) {
935         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
936         snprintf(command, MSG_SIZ, "%s %s", p, params);
937         p = command;
938     }
939     ASSIGN(appData.chessProgram[i], p);
940     appData.isUCI[i] = isUCI;
941     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
942     appData.hasOwnBookUCI[i] = hasBook;
943     if(!nickName[0]) useNick = FALSE;
944     if(useNick) ASSIGN(appData.pgnName[i], nickName);
945     if(addToList) {
946         int len;
947         char quote;
948         q = firstChessProgramNames;
949         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
950         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
951         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
952                         quote, p, quote, appData.directory[i],
953                         useNick ? " -fn \"" : "",
954                         useNick ? nickName : "",
955                         useNick ? "\"" : "",
956                         v1 ? " -firstProtocolVersion 1" : "",
957                         hasBook ? "" : " -fNoOwnBookUCI",
958                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
959                         storeVariant ? " -variant " : "",
960                         storeVariant ? VariantName(gameInfo.variant) : "");
961         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
962         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
963         if(insert != q) insert[-1] = NULLCHAR;
964         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
965         if(q)   free(q);
966         FloatToFront(&appData.recentEngineList, buf);
967     }
968     ReplaceEngine(cps, i);
969 }
970
971 void
972 InitTimeControls ()
973 {
974     int matched, min, sec;
975     /*
976      * Parse timeControl resource
977      */
978     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
979                           appData.movesPerSession)) {
980         char buf[MSG_SIZ];
981         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
982         DisplayFatalError(buf, 0, 2);
983     }
984
985     /*
986      * Parse searchTime resource
987      */
988     if (*appData.searchTime != NULLCHAR) {
989         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
990         if (matched == 1) {
991             searchTime = min * 60;
992         } else if (matched == 2) {
993             searchTime = min * 60 + sec;
994         } else {
995             char buf[MSG_SIZ];
996             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
997             DisplayFatalError(buf, 0, 2);
998         }
999     }
1000 }
1001
1002 void
1003 InitBackEnd1 ()
1004 {
1005
1006     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1007     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1008
1009     GetTimeMark(&programStartTime);
1010     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1011     appData.seedBase = random() + (random()<<15);
1012     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1013
1014     ClearProgramStats();
1015     programStats.ok_to_send = 1;
1016     programStats.seen_stat = 0;
1017
1018     /*
1019      * Initialize game list
1020      */
1021     ListNew(&gameList);
1022
1023
1024     /*
1025      * Internet chess server status
1026      */
1027     if (appData.icsActive) {
1028         appData.matchMode = FALSE;
1029         appData.matchGames = 0;
1030 #if ZIPPY
1031         appData.noChessProgram = !appData.zippyPlay;
1032 #else
1033         appData.zippyPlay = FALSE;
1034         appData.zippyTalk = FALSE;
1035         appData.noChessProgram = TRUE;
1036 #endif
1037         if (*appData.icsHelper != NULLCHAR) {
1038             appData.useTelnet = TRUE;
1039             appData.telnetProgram = appData.icsHelper;
1040         }
1041     } else {
1042         appData.zippyTalk = appData.zippyPlay = FALSE;
1043     }
1044
1045     /* [AS] Initialize pv info list [HGM] and game state */
1046     {
1047         int i, j;
1048
1049         for( i=0; i<=framePtr; i++ ) {
1050             pvInfoList[i].depth = -1;
1051             boards[i][EP_STATUS] = EP_NONE;
1052             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1053         }
1054     }
1055
1056     InitTimeControls();
1057
1058     /* [AS] Adjudication threshold */
1059     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1060
1061     InitEngine(&first, 0);
1062     InitEngine(&second, 1);
1063     CommonEngineInit();
1064
1065     pairing.which = "pairing"; // pairing engine
1066     pairing.pr = NoProc;
1067     pairing.isr = NULL;
1068     pairing.program = appData.pairingEngine;
1069     pairing.host = "localhost";
1070     pairing.dir = ".";
1071
1072     if (appData.icsActive) {
1073         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1074     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1075         appData.clockMode = FALSE;
1076         first.sendTime = second.sendTime = 0;
1077     }
1078
1079 #if ZIPPY
1080     /* Override some settings from environment variables, for backward
1081        compatibility.  Unfortunately it's not feasible to have the env
1082        vars just set defaults, at least in xboard.  Ugh.
1083     */
1084     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1085       ZippyInit();
1086     }
1087 #endif
1088
1089     if (!appData.icsActive) {
1090       char buf[MSG_SIZ];
1091       int len;
1092
1093       /* Check for variants that are supported only in ICS mode,
1094          or not at all.  Some that are accepted here nevertheless
1095          have bugs; see comments below.
1096       */
1097       VariantClass variant = StringToVariant(appData.variant);
1098       switch (variant) {
1099       case VariantBughouse:     /* need four players and two boards */
1100       case VariantKriegspiel:   /* need to hide pieces and move details */
1101         /* case VariantFischeRandom: (Fabien: moved below) */
1102         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1103         if( (len >= MSG_SIZ) && appData.debugMode )
1104           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1105
1106         DisplayFatalError(buf, 0, 2);
1107         return;
1108
1109       case VariantUnknown:
1110       case VariantLoadable:
1111       case Variant29:
1112       case Variant30:
1113       case Variant31:
1114       case Variant32:
1115       case Variant33:
1116       case Variant34:
1117       case Variant35:
1118       case Variant36:
1119       default:
1120         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1121         if( (len >= MSG_SIZ) && appData.debugMode )
1122           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1123
1124         DisplayFatalError(buf, 0, 2);
1125         return;
1126
1127       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1128       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1129       case VariantGothic:     /* [HGM] should work */
1130       case VariantCapablanca: /* [HGM] should work */
1131       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1132       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1133       case VariantKnightmate: /* [HGM] should work */
1134       case VariantCylinder:   /* [HGM] untested */
1135       case VariantFalcon:     /* [HGM] untested */
1136       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1137                                  offboard interposition not understood */
1138       case VariantNormal:     /* definitely works! */
1139       case VariantWildCastle: /* pieces not automatically shuffled */
1140       case VariantNoCastle:   /* pieces not automatically shuffled */
1141       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1142       case VariantLosers:     /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantSuicide:    /* should work except for win condition,
1145                                  and doesn't know captures are mandatory */
1146       case VariantGiveaway:   /* should work except for win condition,
1147                                  and doesn't know captures are mandatory */
1148       case VariantTwoKings:   /* should work */
1149       case VariantAtomic:     /* should work except for win condition */
1150       case Variant3Check:     /* should work except for win condition */
1151       case VariantShatranj:   /* should work except for all win conditions */
1152       case VariantMakruk:     /* should work except for draw countdown */
1153       case VariantBerolina:   /* might work if TestLegality is off */
1154       case VariantCapaRandom: /* should work */
1155       case VariantJanus:      /* should work */
1156       case VariantSuper:      /* experimental */
1157       case VariantGreat:      /* experimental, requires legality testing to be off */
1158       case VariantSChess:     /* S-Chess, should work */
1159       case VariantGrand:      /* should work */
1160       case VariantSpartan:    /* should work */
1161         break;
1162       }
1163     }
1164
1165 }
1166
1167 int
1168 NextIntegerFromString (char ** str, long * value)
1169 {
1170     int result = -1;
1171     char * s = *str;
1172
1173     while( *s == ' ' || *s == '\t' ) {
1174         s++;
1175     }
1176
1177     *value = 0;
1178
1179     if( *s >= '0' && *s <= '9' ) {
1180         while( *s >= '0' && *s <= '9' ) {
1181             *value = *value * 10 + (*s - '0');
1182             s++;
1183         }
1184
1185         result = 0;
1186     }
1187
1188     *str = s;
1189
1190     return result;
1191 }
1192
1193 int
1194 NextTimeControlFromString (char ** str, long * value)
1195 {
1196     long temp;
1197     int result = NextIntegerFromString( str, &temp );
1198
1199     if( result == 0 ) {
1200         *value = temp * 60; /* Minutes */
1201         if( **str == ':' ) {
1202             (*str)++;
1203             result = NextIntegerFromString( str, &temp );
1204             *value += temp; /* Seconds */
1205         }
1206     }
1207
1208     return result;
1209 }
1210
1211 int
1212 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1213 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1214     int result = -1, type = 0; long temp, temp2;
1215
1216     if(**str != ':') return -1; // old params remain in force!
1217     (*str)++;
1218     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1219     if( NextIntegerFromString( str, &temp ) ) return -1;
1220     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1221
1222     if(**str != '/') {
1223         /* time only: incremental or sudden-death time control */
1224         if(**str == '+') { /* increment follows; read it */
1225             (*str)++;
1226             if(**str == '!') type = *(*str)++; // Bronstein TC
1227             if(result = NextIntegerFromString( str, &temp2)) return -1;
1228             *inc = temp2 * 1000;
1229             if(**str == '.') { // read fraction of increment
1230                 char *start = ++(*str);
1231                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1232                 temp2 *= 1000;
1233                 while(start++ < *str) temp2 /= 10;
1234                 *inc += temp2;
1235             }
1236         } else *inc = 0;
1237         *moves = 0; *tc = temp * 1000; *incType = type;
1238         return 0;
1239     }
1240
1241     (*str)++; /* classical time control */
1242     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1243
1244     if(result == 0) {
1245         *moves = temp;
1246         *tc    = temp2 * 1000;
1247         *inc   = 0;
1248         *incType = type;
1249     }
1250     return result;
1251 }
1252
1253 int
1254 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1255 {   /* [HGM] get time to add from the multi-session time-control string */
1256     int incType, moves=1; /* kludge to force reading of first session */
1257     long time, increment;
1258     char *s = tcString;
1259
1260     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1261     do {
1262         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1263         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1264         if(movenr == -1) return time;    /* last move before new session     */
1265         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1266         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1267         if(!moves) return increment;     /* current session is incremental   */
1268         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1269     } while(movenr >= -1);               /* try again for next session       */
1270
1271     return 0; // no new time quota on this move
1272 }
1273
1274 int
1275 ParseTimeControl (char *tc, float ti, int mps)
1276 {
1277   long tc1;
1278   long tc2;
1279   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1280   int min, sec=0;
1281
1282   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1283   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1284       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1285   if(ti > 0) {
1286
1287     if(mps)
1288       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1289     else
1290       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1291   } else {
1292     if(mps)
1293       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1294     else
1295       snprintf(buf, MSG_SIZ, ":%s", mytc);
1296   }
1297   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1298
1299   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1300     return FALSE;
1301   }
1302
1303   if( *tc == '/' ) {
1304     /* Parse second time control */
1305     tc++;
1306
1307     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1308       return FALSE;
1309     }
1310
1311     if( tc2 == 0 ) {
1312       return FALSE;
1313     }
1314
1315     timeControl_2 = tc2 * 1000;
1316   }
1317   else {
1318     timeControl_2 = 0;
1319   }
1320
1321   if( tc1 == 0 ) {
1322     return FALSE;
1323   }
1324
1325   timeControl = tc1 * 1000;
1326
1327   if (ti >= 0) {
1328     timeIncrement = ti * 1000;  /* convert to ms */
1329     movesPerSession = 0;
1330   } else {
1331     timeIncrement = 0;
1332     movesPerSession = mps;
1333   }
1334   return TRUE;
1335 }
1336
1337 void
1338 InitBackEnd2 ()
1339 {
1340     if (appData.debugMode) {
1341         fprintf(debugFP, "%s\n", programVersion);
1342     }
1343     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1344
1345     set_cont_sequence(appData.wrapContSeq);
1346     if (appData.matchGames > 0) {
1347         appData.matchMode = TRUE;
1348     } else if (appData.matchMode) {
1349         appData.matchGames = 1;
1350     }
1351     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1352         appData.matchGames = appData.sameColorGames;
1353     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1354         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1355         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1356     }
1357     Reset(TRUE, FALSE);
1358     if (appData.noChessProgram || first.protocolVersion == 1) {
1359       InitBackEnd3();
1360     } else {
1361       /* kludge: allow timeout for initial "feature" commands */
1362       FreezeUI();
1363       DisplayMessage("", _("Starting chess program"));
1364       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1365     }
1366 }
1367
1368 int
1369 CalculateIndex (int index, int gameNr)
1370 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1371     int res;
1372     if(index > 0) return index; // fixed nmber
1373     if(index == 0) return 1;
1374     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1375     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1376     return res;
1377 }
1378
1379 int
1380 LoadGameOrPosition (int gameNr)
1381 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1382     if (*appData.loadGameFile != NULLCHAR) {
1383         if (!LoadGameFromFile(appData.loadGameFile,
1384                 CalculateIndex(appData.loadGameIndex, gameNr),
1385                               appData.loadGameFile, FALSE)) {
1386             DisplayFatalError(_("Bad game file"), 0, 1);
1387             return 0;
1388         }
1389     } else if (*appData.loadPositionFile != NULLCHAR) {
1390         if (!LoadPositionFromFile(appData.loadPositionFile,
1391                 CalculateIndex(appData.loadPositionIndex, gameNr),
1392                                   appData.loadPositionFile)) {
1393             DisplayFatalError(_("Bad position file"), 0, 1);
1394             return 0;
1395         }
1396     }
1397     return 1;
1398 }
1399
1400 void
1401 ReserveGame (int gameNr, char resChar)
1402 {
1403     FILE *tf = fopen(appData.tourneyFile, "r+");
1404     char *p, *q, c, buf[MSG_SIZ];
1405     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1406     safeStrCpy(buf, lastMsg, MSG_SIZ);
1407     DisplayMessage(_("Pick new game"), "");
1408     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1409     ParseArgsFromFile(tf);
1410     p = q = appData.results;
1411     if(appData.debugMode) {
1412       char *r = appData.participants;
1413       fprintf(debugFP, "results = '%s'\n", p);
1414       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1415       fprintf(debugFP, "\n");
1416     }
1417     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1418     nextGame = q - p;
1419     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1420     safeStrCpy(q, p, strlen(p) + 2);
1421     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1422     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1424         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1425         q[nextGame] = '*';
1426     }
1427     fseek(tf, -(strlen(p)+4), SEEK_END);
1428     c = fgetc(tf);
1429     if(c != '"') // depending on DOS or Unix line endings we can be one off
1430          fseek(tf, -(strlen(p)+2), SEEK_END);
1431     else fseek(tf, -(strlen(p)+3), SEEK_END);
1432     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1433     DisplayMessage(buf, "");
1434     free(p); appData.results = q;
1435     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1436        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1437       int round = appData.defaultMatchGames * appData.tourneyType;
1438       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1439          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1440         UnloadEngine(&first);  // next game belongs to other pairing;
1441         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1442     }
1443     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1444 }
1445
1446 void
1447 MatchEvent (int mode)
1448 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1449         int dummy;
1450         if(matchMode) { // already in match mode: switch it off
1451             abortMatch = TRUE;
1452             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1453             return;
1454         }
1455 //      if(gameMode != BeginningOfGame) {
1456 //          DisplayError(_("You can only start a match from the initial position."), 0);
1457 //          return;
1458 //      }
1459         abortMatch = FALSE;
1460         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1461         /* Set up machine vs. machine match */
1462         nextGame = 0;
1463         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1464         if(appData.tourneyFile[0]) {
1465             ReserveGame(-1, 0);
1466             if(nextGame > appData.matchGames) {
1467                 char buf[MSG_SIZ];
1468                 if(strchr(appData.results, '*') == NULL) {
1469                     FILE *f;
1470                     appData.tourneyCycles++;
1471                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1472                         fclose(f);
1473                         NextTourneyGame(-1, &dummy);
1474                         ReserveGame(-1, 0);
1475                         if(nextGame <= appData.matchGames) {
1476                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1477                             matchMode = mode;
1478                             ScheduleDelayedEvent(NextMatchGame, 10000);
1479                             return;
1480                         }
1481                     }
1482                 }
1483                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1484                 DisplayError(buf, 0);
1485                 appData.tourneyFile[0] = 0;
1486                 return;
1487             }
1488         } else
1489         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1490             DisplayFatalError(_("Can't have a match with no chess programs"),
1491                               0, 2);
1492             return;
1493         }
1494         matchMode = mode;
1495         matchGame = roundNr = 1;
1496         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1497         NextMatchGame();
1498 }
1499
1500 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1501
1502 void
1503 InitBackEnd3 P((void))
1504 {
1505     GameMode initialMode;
1506     char buf[MSG_SIZ];
1507     int err, len;
1508
1509     InitChessProgram(&first, startedFromSetupPosition);
1510
1511     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1512         free(programVersion);
1513         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1514         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1515         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1516     }
1517
1518     if (appData.icsActive) {
1519 #ifdef WIN32
1520         /* [DM] Make a console window if needed [HGM] merged ifs */
1521         ConsoleCreate();
1522 #endif
1523         err = establish();
1524         if (err != 0)
1525           {
1526             if (*appData.icsCommPort != NULLCHAR)
1527               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1528                              appData.icsCommPort);
1529             else
1530               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1531                         appData.icsHost, appData.icsPort);
1532
1533             if( (len >= MSG_SIZ) && appData.debugMode )
1534               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1535
1536             DisplayFatalError(buf, err, 1);
1537             return;
1538         }
1539         SetICSMode();
1540         telnetISR =
1541           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1542         fromUserISR =
1543           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1544         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1545             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1546     } else if (appData.noChessProgram) {
1547         SetNCPMode();
1548     } else {
1549         SetGNUMode();
1550     }
1551
1552     if (*appData.cmailGameName != NULLCHAR) {
1553         SetCmailMode();
1554         OpenLoopback(&cmailPR);
1555         cmailISR =
1556           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1557     }
1558
1559     ThawUI();
1560     DisplayMessage("", "");
1561     if (StrCaseCmp(appData.initialMode, "") == 0) {
1562       initialMode = BeginningOfGame;
1563       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1564         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1565         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1566         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1567         ModeHighlight();
1568       }
1569     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1570       initialMode = TwoMachinesPlay;
1571     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1572       initialMode = AnalyzeFile;
1573     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1574       initialMode = AnalyzeMode;
1575     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1576       initialMode = MachinePlaysWhite;
1577     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1578       initialMode = MachinePlaysBlack;
1579     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1580       initialMode = EditGame;
1581     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1582       initialMode = EditPosition;
1583     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1584       initialMode = Training;
1585     } else {
1586       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1587       if( (len >= MSG_SIZ) && appData.debugMode )
1588         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1589
1590       DisplayFatalError(buf, 0, 2);
1591       return;
1592     }
1593
1594     if (appData.matchMode) {
1595         if(appData.tourneyFile[0]) { // start tourney from command line
1596             FILE *f;
1597             if(f = fopen(appData.tourneyFile, "r")) {
1598                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1599                 fclose(f);
1600                 appData.clockMode = TRUE;
1601                 SetGNUMode();
1602             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1603         }
1604         MatchEvent(TRUE);
1605     } else if (*appData.cmailGameName != NULLCHAR) {
1606         /* Set up cmail mode */
1607         ReloadCmailMsgEvent(TRUE);
1608     } else {
1609         /* Set up other modes */
1610         if (initialMode == AnalyzeFile) {
1611           if (*appData.loadGameFile == NULLCHAR) {
1612             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1613             return;
1614           }
1615         }
1616         if (*appData.loadGameFile != NULLCHAR) {
1617             (void) LoadGameFromFile(appData.loadGameFile,
1618                                     appData.loadGameIndex,
1619                                     appData.loadGameFile, TRUE);
1620         } else if (*appData.loadPositionFile != NULLCHAR) {
1621             (void) LoadPositionFromFile(appData.loadPositionFile,
1622                                         appData.loadPositionIndex,
1623                                         appData.loadPositionFile);
1624             /* [HGM] try to make self-starting even after FEN load */
1625             /* to allow automatic setup of fairy variants with wtm */
1626             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1627                 gameMode = BeginningOfGame;
1628                 setboardSpoiledMachineBlack = 1;
1629             }
1630             /* [HGM] loadPos: make that every new game uses the setup */
1631             /* from file as long as we do not switch variant          */
1632             if(!blackPlaysFirst) {
1633                 startedFromPositionFile = TRUE;
1634                 CopyBoard(filePosition, boards[0]);
1635             }
1636         }
1637         if (initialMode == AnalyzeMode) {
1638           if (appData.noChessProgram) {
1639             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1644             return;
1645           }
1646           AnalyzeModeEvent();
1647         } else if (initialMode == AnalyzeFile) {
1648           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1649           ShowThinkingEvent();
1650           AnalyzeFileEvent();
1651           AnalysisPeriodicEvent(1);
1652         } else if (initialMode == MachinePlaysWhite) {
1653           if (appData.noChessProgram) {
1654             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1655                               0, 2);
1656             return;
1657           }
1658           if (appData.icsActive) {
1659             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1660                               0, 2);
1661             return;
1662           }
1663           MachineWhiteEvent();
1664         } else if (initialMode == MachinePlaysBlack) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           MachineBlackEvent();
1676         } else if (initialMode == TwoMachinesPlay) {
1677           if (appData.noChessProgram) {
1678             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1679                               0, 2);
1680             return;
1681           }
1682           if (appData.icsActive) {
1683             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1684                               0, 2);
1685             return;
1686           }
1687           TwoMachinesEvent();
1688         } else if (initialMode == EditGame) {
1689           EditGameEvent();
1690         } else if (initialMode == EditPosition) {
1691           EditPositionEvent();
1692         } else if (initialMode == Training) {
1693           if (*appData.loadGameFile == NULLCHAR) {
1694             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1695             return;
1696           }
1697           TrainingEvent();
1698         }
1699     }
1700 }
1701
1702 void
1703 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1704 {
1705     DisplayBook(current+1);
1706
1707     MoveHistorySet( movelist, first, last, current, pvInfoList );
1708
1709     EvalGraphSet( first, last, current, pvInfoList );
1710
1711     MakeEngineOutputTitle();
1712 }
1713
1714 /*
1715  * Establish will establish a contact to a remote host.port.
1716  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1717  *  used to talk to the host.
1718  * Returns 0 if okay, error code if not.
1719  */
1720 int
1721 establish ()
1722 {
1723     char buf[MSG_SIZ];
1724
1725     if (*appData.icsCommPort != NULLCHAR) {
1726         /* Talk to the host through a serial comm port */
1727         return OpenCommPort(appData.icsCommPort, &icsPR);
1728
1729     } else if (*appData.gateway != NULLCHAR) {
1730         if (*appData.remoteShell == NULLCHAR) {
1731             /* Use the rcmd protocol to run telnet program on a gateway host */
1732             snprintf(buf, sizeof(buf), "%s %s %s",
1733                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1734             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1735
1736         } else {
1737             /* Use the rsh program to run telnet program on a gateway host */
1738             if (*appData.remoteUser == NULLCHAR) {
1739                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1740                         appData.gateway, appData.telnetProgram,
1741                         appData.icsHost, appData.icsPort);
1742             } else {
1743                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1744                         appData.remoteShell, appData.gateway,
1745                         appData.remoteUser, appData.telnetProgram,
1746                         appData.icsHost, appData.icsPort);
1747             }
1748             return StartChildProcess(buf, "", &icsPR);
1749
1750         }
1751     } else if (appData.useTelnet) {
1752         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1753
1754     } else {
1755         /* TCP socket interface differs somewhat between
1756            Unix and NT; handle details in the front end.
1757            */
1758         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1759     }
1760 }
1761
1762 void
1763 EscapeExpand (char *p, char *q)
1764 {       // [HGM] initstring: routine to shape up string arguments
1765         while(*p++ = *q++) if(p[-1] == '\\')
1766             switch(*q++) {
1767                 case 'n': p[-1] = '\n'; break;
1768                 case 'r': p[-1] = '\r'; break;
1769                 case 't': p[-1] = '\t'; break;
1770                 case '\\': p[-1] = '\\'; break;
1771                 case 0: *p = 0; return;
1772                 default: p[-1] = q[-1]; break;
1773             }
1774 }
1775
1776 void
1777 show_bytes (FILE *fp, char *buf, int count)
1778 {
1779     while (count--) {
1780         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1781             fprintf(fp, "\\%03o", *buf & 0xff);
1782         } else {
1783             putc(*buf, fp);
1784         }
1785         buf++;
1786     }
1787     fflush(fp);
1788 }
1789
1790 /* Returns an errno value */
1791 int
1792 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1793 {
1794     char buf[8192], *p, *q, *buflim;
1795     int left, newcount, outcount;
1796
1797     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1798         *appData.gateway != NULLCHAR) {
1799         if (appData.debugMode) {
1800             fprintf(debugFP, ">ICS: ");
1801             show_bytes(debugFP, message, count);
1802             fprintf(debugFP, "\n");
1803         }
1804         return OutputToProcess(pr, message, count, outError);
1805     }
1806
1807     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1808     p = message;
1809     q = buf;
1810     left = count;
1811     newcount = 0;
1812     while (left) {
1813         if (q >= buflim) {
1814             if (appData.debugMode) {
1815                 fprintf(debugFP, ">ICS: ");
1816                 show_bytes(debugFP, buf, newcount);
1817                 fprintf(debugFP, "\n");
1818             }
1819             outcount = OutputToProcess(pr, buf, newcount, outError);
1820             if (outcount < newcount) return -1; /* to be sure */
1821             q = buf;
1822             newcount = 0;
1823         }
1824         if (*p == '\n') {
1825             *q++ = '\r';
1826             newcount++;
1827         } else if (((unsigned char) *p) == TN_IAC) {
1828             *q++ = (char) TN_IAC;
1829             newcount ++;
1830         }
1831         *q++ = *p++;
1832         newcount++;
1833         left--;
1834     }
1835     if (appData.debugMode) {
1836         fprintf(debugFP, ">ICS: ");
1837         show_bytes(debugFP, buf, newcount);
1838         fprintf(debugFP, "\n");
1839     }
1840     outcount = OutputToProcess(pr, buf, newcount, outError);
1841     if (outcount < newcount) return -1; /* to be sure */
1842     return count;
1843 }
1844
1845 void
1846 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1847 {
1848     int outError, outCount;
1849     static int gotEof = 0;
1850     static FILE *ini;
1851
1852     /* Pass data read from player on to ICS */
1853     if (count > 0) {
1854         gotEof = 0;
1855         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1856         if (outCount < count) {
1857             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1858         }
1859         if(have_sent_ICS_logon == 2) {
1860           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1861             fprintf(ini, "%s", message);
1862             have_sent_ICS_logon = 3;
1863           } else
1864             have_sent_ICS_logon = 1;
1865         } else if(have_sent_ICS_logon == 3) {
1866             fprintf(ini, "%s", message);
1867             fclose(ini);
1868           have_sent_ICS_logon = 1;
1869         }
1870     } else if (count < 0) {
1871         RemoveInputSource(isr);
1872         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1873     } else if (gotEof++ > 0) {
1874         RemoveInputSource(isr);
1875         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1876     }
1877 }
1878
1879 void
1880 KeepAlive ()
1881 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1882     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1883     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1884     SendToICS("date\n");
1885     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1886 }
1887
1888 /* added routine for printf style output to ics */
1889 void
1890 ics_printf (char *format, ...)
1891 {
1892     char buffer[MSG_SIZ];
1893     va_list args;
1894
1895     va_start(args, format);
1896     vsnprintf(buffer, sizeof(buffer), format, args);
1897     buffer[sizeof(buffer)-1] = '\0';
1898     SendToICS(buffer);
1899     va_end(args);
1900 }
1901
1902 void
1903 SendToICS (char *s)
1904 {
1905     int count, outCount, outError;
1906
1907     if (icsPR == NoProc) return;
1908
1909     count = strlen(s);
1910     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1911     if (outCount < count) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 /* This is used for sending logon scripts to the ICS. Sending
1917    without a delay causes problems when using timestamp on ICC
1918    (at least on my machine). */
1919 void
1920 SendToICSDelayed (char *s, long msdelay)
1921 {
1922     int count, outCount, outError;
1923
1924     if (icsPR == NoProc) return;
1925
1926     count = strlen(s);
1927     if (appData.debugMode) {
1928         fprintf(debugFP, ">ICS: ");
1929         show_bytes(debugFP, s, count);
1930         fprintf(debugFP, "\n");
1931     }
1932     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1933                                       msdelay);
1934     if (outCount < count) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939
1940 /* Remove all highlighting escape sequences in s
1941    Also deletes any suffix starting with '('
1942    */
1943 char *
1944 StripHighlightAndTitle (char *s)
1945 {
1946     static char retbuf[MSG_SIZ];
1947     char *p = retbuf;
1948
1949     while (*s != NULLCHAR) {
1950         while (*s == '\033') {
1951             while (*s != NULLCHAR && !isalpha(*s)) s++;
1952             if (*s != NULLCHAR) s++;
1953         }
1954         while (*s != NULLCHAR && *s != '\033') {
1955             if (*s == '(' || *s == '[') {
1956                 *p = NULLCHAR;
1957                 return retbuf;
1958             }
1959             *p++ = *s++;
1960         }
1961     }
1962     *p = NULLCHAR;
1963     return retbuf;
1964 }
1965
1966 /* Remove all highlighting escape sequences in s */
1967 char *
1968 StripHighlight (char *s)
1969 {
1970     static char retbuf[MSG_SIZ];
1971     char *p = retbuf;
1972
1973     while (*s != NULLCHAR) {
1974         while (*s == '\033') {
1975             while (*s != NULLCHAR && !isalpha(*s)) s++;
1976             if (*s != NULLCHAR) s++;
1977         }
1978         while (*s != NULLCHAR && *s != '\033') {
1979             *p++ = *s++;
1980         }
1981     }
1982     *p = NULLCHAR;
1983     return retbuf;
1984 }
1985
1986 char *variantNames[] = VARIANT_NAMES;
1987 char *
1988 VariantName (VariantClass v)
1989 {
1990     return variantNames[v];
1991 }
1992
1993
1994 /* Identify a variant from the strings the chess servers use or the
1995    PGN Variant tag names we use. */
1996 VariantClass
1997 StringToVariant (char *e)
1998 {
1999     char *p;
2000     int wnum = -1;
2001     VariantClass v = VariantNormal;
2002     int i, found = FALSE;
2003     char buf[MSG_SIZ];
2004     int len;
2005
2006     if (!e) return v;
2007
2008     /* [HGM] skip over optional board-size prefixes */
2009     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2010         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2011         while( *e++ != '_');
2012     }
2013
2014     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2015         v = VariantNormal;
2016         found = TRUE;
2017     } else
2018     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2019       if (StrCaseStr(e, variantNames[i])) {
2020         v = (VariantClass) i;
2021         found = TRUE;
2022         break;
2023       }
2024     }
2025
2026     if (!found) {
2027       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2028           || StrCaseStr(e, "wild/fr")
2029           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2030         v = VariantFischeRandom;
2031       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2032                  (i = 1, p = StrCaseStr(e, "w"))) {
2033         p += i;
2034         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2035         if (isdigit(*p)) {
2036           wnum = atoi(p);
2037         } else {
2038           wnum = -1;
2039         }
2040         switch (wnum) {
2041         case 0: /* FICS only, actually */
2042         case 1:
2043           /* Castling legal even if K starts on d-file */
2044           v = VariantWildCastle;
2045           break;
2046         case 2:
2047         case 3:
2048         case 4:
2049           /* Castling illegal even if K & R happen to start in
2050              normal positions. */
2051           v = VariantNoCastle;
2052           break;
2053         case 5:
2054         case 7:
2055         case 8:
2056         case 10:
2057         case 11:
2058         case 12:
2059         case 13:
2060         case 14:
2061         case 15:
2062         case 18:
2063         case 19:
2064           /* Castling legal iff K & R start in normal positions */
2065           v = VariantNormal;
2066           break;
2067         case 6:
2068         case 20:
2069         case 21:
2070           /* Special wilds for position setup; unclear what to do here */
2071           v = VariantLoadable;
2072           break;
2073         case 9:
2074           /* Bizarre ICC game */
2075           v = VariantTwoKings;
2076           break;
2077         case 16:
2078           v = VariantKriegspiel;
2079           break;
2080         case 17:
2081           v = VariantLosers;
2082           break;
2083         case 22:
2084           v = VariantFischeRandom;
2085           break;
2086         case 23:
2087           v = VariantCrazyhouse;
2088           break;
2089         case 24:
2090           v = VariantBughouse;
2091           break;
2092         case 25:
2093           v = Variant3Check;
2094           break;
2095         case 26:
2096           /* Not quite the same as FICS suicide! */
2097           v = VariantGiveaway;
2098           break;
2099         case 27:
2100           v = VariantAtomic;
2101           break;
2102         case 28:
2103           v = VariantShatranj;
2104           break;
2105
2106         /* Temporary names for future ICC types.  The name *will* change in
2107            the next xboard/WinBoard release after ICC defines it. */
2108         case 29:
2109           v = Variant29;
2110           break;
2111         case 30:
2112           v = Variant30;
2113           break;
2114         case 31:
2115           v = Variant31;
2116           break;
2117         case 32:
2118           v = Variant32;
2119           break;
2120         case 33:
2121           v = Variant33;
2122           break;
2123         case 34:
2124           v = Variant34;
2125           break;
2126         case 35:
2127           v = Variant35;
2128           break;
2129         case 36:
2130           v = Variant36;
2131           break;
2132         case 37:
2133           v = VariantShogi;
2134           break;
2135         case 38:
2136           v = VariantXiangqi;
2137           break;
2138         case 39:
2139           v = VariantCourier;
2140           break;
2141         case 40:
2142           v = VariantGothic;
2143           break;
2144         case 41:
2145           v = VariantCapablanca;
2146           break;
2147         case 42:
2148           v = VariantKnightmate;
2149           break;
2150         case 43:
2151           v = VariantFairy;
2152           break;
2153         case 44:
2154           v = VariantCylinder;
2155           break;
2156         case 45:
2157           v = VariantFalcon;
2158           break;
2159         case 46:
2160           v = VariantCapaRandom;
2161           break;
2162         case 47:
2163           v = VariantBerolina;
2164           break;
2165         case 48:
2166           v = VariantJanus;
2167           break;
2168         case 49:
2169           v = VariantSuper;
2170           break;
2171         case 50:
2172           v = VariantGreat;
2173           break;
2174         case -1:
2175           /* Found "wild" or "w" in the string but no number;
2176              must assume it's normal chess. */
2177           v = VariantNormal;
2178           break;
2179         default:
2180           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2181           if( (len >= MSG_SIZ) && appData.debugMode )
2182             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2183
2184           DisplayError(buf, 0);
2185           v = VariantUnknown;
2186           break;
2187         }
2188       }
2189     }
2190     if (appData.debugMode) {
2191       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2192               e, wnum, VariantName(v));
2193     }
2194     return v;
2195 }
2196
2197 static int leftover_start = 0, leftover_len = 0;
2198 char star_match[STAR_MATCH_N][MSG_SIZ];
2199
2200 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2201    advance *index beyond it, and set leftover_start to the new value of
2202    *index; else return FALSE.  If pattern contains the character '*', it
2203    matches any sequence of characters not containing '\r', '\n', or the
2204    character following the '*' (if any), and the matched sequence(s) are
2205    copied into star_match.
2206    */
2207 int
2208 looking_at ( char *buf, int *index, char *pattern)
2209 {
2210     char *bufp = &buf[*index], *patternp = pattern;
2211     int star_count = 0;
2212     char *matchp = star_match[0];
2213
2214     for (;;) {
2215         if (*patternp == NULLCHAR) {
2216             *index = leftover_start = bufp - buf;
2217             *matchp = NULLCHAR;
2218             return TRUE;
2219         }
2220         if (*bufp == NULLCHAR) return FALSE;
2221         if (*patternp == '*') {
2222             if (*bufp == *(patternp + 1)) {
2223                 *matchp = NULLCHAR;
2224                 matchp = star_match[++star_count];
2225                 patternp += 2;
2226                 bufp++;
2227                 continue;
2228             } else if (*bufp == '\n' || *bufp == '\r') {
2229                 patternp++;
2230                 if (*patternp == NULLCHAR)
2231                   continue;
2232                 else
2233                   return FALSE;
2234             } else {
2235                 *matchp++ = *bufp++;
2236                 continue;
2237             }
2238         }
2239         if (*patternp != *bufp) return FALSE;
2240         patternp++;
2241         bufp++;
2242     }
2243 }
2244
2245 void
2246 SendToPlayer (char *data, int length)
2247 {
2248     int error, outCount;
2249     outCount = OutputToProcess(NoProc, data, length, &error);
2250     if (outCount < length) {
2251         DisplayFatalError(_("Error writing to display"), error, 1);
2252     }
2253 }
2254
2255 void
2256 PackHolding (char packed[], char *holding)
2257 {
2258     char *p = holding;
2259     char *q = packed;
2260     int runlength = 0;
2261     int curr = 9999;
2262     do {
2263         if (*p == curr) {
2264             runlength++;
2265         } else {
2266             switch (runlength) {
2267               case 0:
2268                 break;
2269               case 1:
2270                 *q++ = curr;
2271                 break;
2272               case 2:
2273                 *q++ = curr;
2274                 *q++ = curr;
2275                 break;
2276               default:
2277                 sprintf(q, "%d", runlength);
2278                 while (*q) q++;
2279                 *q++ = curr;
2280                 break;
2281             }
2282             runlength = 1;
2283             curr = *p;
2284         }
2285     } while (*p++);
2286     *q = NULLCHAR;
2287 }
2288
2289 /* Telnet protocol requests from the front end */
2290 void
2291 TelnetRequest (unsigned char ddww, unsigned char option)
2292 {
2293     unsigned char msg[3];
2294     int outCount, outError;
2295
2296     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2297
2298     if (appData.debugMode) {
2299         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2300         switch (ddww) {
2301           case TN_DO:
2302             ddwwStr = "DO";
2303             break;
2304           case TN_DONT:
2305             ddwwStr = "DONT";
2306             break;
2307           case TN_WILL:
2308             ddwwStr = "WILL";
2309             break;
2310           case TN_WONT:
2311             ddwwStr = "WONT";
2312             break;
2313           default:
2314             ddwwStr = buf1;
2315             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2316             break;
2317         }
2318         switch (option) {
2319           case TN_ECHO:
2320             optionStr = "ECHO";
2321             break;
2322           default:
2323             optionStr = buf2;
2324             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2325             break;
2326         }
2327         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2328     }
2329     msg[0] = TN_IAC;
2330     msg[1] = ddww;
2331     msg[2] = option;
2332     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2333     if (outCount < 3) {
2334         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2335     }
2336 }
2337
2338 void
2339 DoEcho ()
2340 {
2341     if (!appData.icsActive) return;
2342     TelnetRequest(TN_DO, TN_ECHO);
2343 }
2344
2345 void
2346 DontEcho ()
2347 {
2348     if (!appData.icsActive) return;
2349     TelnetRequest(TN_DONT, TN_ECHO);
2350 }
2351
2352 void
2353 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2354 {
2355     /* put the holdings sent to us by the server on the board holdings area */
2356     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2357     char p;
2358     ChessSquare piece;
2359
2360     if(gameInfo.holdingsWidth < 2)  return;
2361     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2362         return; // prevent overwriting by pre-board holdings
2363
2364     if( (int)lowestPiece >= BlackPawn ) {
2365         holdingsColumn = 0;
2366         countsColumn = 1;
2367         holdingsStartRow = BOARD_HEIGHT-1;
2368         direction = -1;
2369     } else {
2370         holdingsColumn = BOARD_WIDTH-1;
2371         countsColumn = BOARD_WIDTH-2;
2372         holdingsStartRow = 0;
2373         direction = 1;
2374     }
2375
2376     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2377         board[i][holdingsColumn] = EmptySquare;
2378         board[i][countsColumn]   = (ChessSquare) 0;
2379     }
2380     while( (p=*holdings++) != NULLCHAR ) {
2381         piece = CharToPiece( ToUpper(p) );
2382         if(piece == EmptySquare) continue;
2383         /*j = (int) piece - (int) WhitePawn;*/
2384         j = PieceToNumber(piece);
2385         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2386         if(j < 0) continue;               /* should not happen */
2387         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2388         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2389         board[holdingsStartRow+j*direction][countsColumn]++;
2390     }
2391 }
2392
2393
2394 void
2395 VariantSwitch (Board board, VariantClass newVariant)
2396 {
2397    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2398    static Board oldBoard;
2399
2400    startedFromPositionFile = FALSE;
2401    if(gameInfo.variant == newVariant) return;
2402
2403    /* [HGM] This routine is called each time an assignment is made to
2404     * gameInfo.variant during a game, to make sure the board sizes
2405     * are set to match the new variant. If that means adding or deleting
2406     * holdings, we shift the playing board accordingly
2407     * This kludge is needed because in ICS observe mode, we get boards
2408     * of an ongoing game without knowing the variant, and learn about the
2409     * latter only later. This can be because of the move list we requested,
2410     * in which case the game history is refilled from the beginning anyway,
2411     * but also when receiving holdings of a crazyhouse game. In the latter
2412     * case we want to add those holdings to the already received position.
2413     */
2414
2415
2416    if (appData.debugMode) {
2417      fprintf(debugFP, "Switch board from %s to %s\n",
2418              VariantName(gameInfo.variant), VariantName(newVariant));
2419      setbuf(debugFP, NULL);
2420    }
2421    shuffleOpenings = 0;       /* [HGM] shuffle */
2422    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2423    switch(newVariant)
2424      {
2425      case VariantShogi:
2426        newWidth = 9;  newHeight = 9;
2427        gameInfo.holdingsSize = 7;
2428      case VariantBughouse:
2429      case VariantCrazyhouse:
2430        newHoldingsWidth = 2; break;
2431      case VariantGreat:
2432        newWidth = 10;
2433      case VariantSuper:
2434        newHoldingsWidth = 2;
2435        gameInfo.holdingsSize = 8;
2436        break;
2437      case VariantGothic:
2438      case VariantCapablanca:
2439      case VariantCapaRandom:
2440        newWidth = 10;
2441      default:
2442        newHoldingsWidth = gameInfo.holdingsSize = 0;
2443      };
2444
2445    if(newWidth  != gameInfo.boardWidth  ||
2446       newHeight != gameInfo.boardHeight ||
2447       newHoldingsWidth != gameInfo.holdingsWidth ) {
2448
2449      /* shift position to new playing area, if needed */
2450      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2451        for(i=0; i<BOARD_HEIGHT; i++)
2452          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2453            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2454              board[i][j];
2455        for(i=0; i<newHeight; i++) {
2456          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2457          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2458        }
2459      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2460        for(i=0; i<BOARD_HEIGHT; i++)
2461          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2462            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2463              board[i][j];
2464      }
2465      board[HOLDINGS_SET] = 0;
2466      gameInfo.boardWidth  = newWidth;
2467      gameInfo.boardHeight = newHeight;
2468      gameInfo.holdingsWidth = newHoldingsWidth;
2469      gameInfo.variant = newVariant;
2470      InitDrawingSizes(-2, 0);
2471    } else gameInfo.variant = newVariant;
2472    CopyBoard(oldBoard, board);   // remember correctly formatted board
2473      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2474    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2475 }
2476
2477 static int loggedOn = FALSE;
2478
2479 /*-- Game start info cache: --*/
2480 int gs_gamenum;
2481 char gs_kind[MSG_SIZ];
2482 static char player1Name[128] = "";
2483 static char player2Name[128] = "";
2484 static char cont_seq[] = "\n\\   ";
2485 static int player1Rating = -1;
2486 static int player2Rating = -1;
2487 /*----------------------------*/
2488
2489 ColorClass curColor = ColorNormal;
2490 int suppressKibitz = 0;
2491
2492 // [HGM] seekgraph
2493 Boolean soughtPending = FALSE;
2494 Boolean seekGraphUp;
2495 #define MAX_SEEK_ADS 200
2496 #define SQUARE 0x80
2497 char *seekAdList[MAX_SEEK_ADS];
2498 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2499 float tcList[MAX_SEEK_ADS];
2500 char colorList[MAX_SEEK_ADS];
2501 int nrOfSeekAds = 0;
2502 int minRating = 1010, maxRating = 2800;
2503 int hMargin = 10, vMargin = 20, h, w;
2504 extern int squareSize, lineGap;
2505
2506 void
2507 PlotSeekAd (int i)
2508 {
2509         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2510         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2511         if(r < minRating+100 && r >=0 ) r = minRating+100;
2512         if(r > maxRating) r = maxRating;
2513         if(tc < 1.f) tc = 1.f;
2514         if(tc > 95.f) tc = 95.f;
2515         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2516         y = ((double)r - minRating)/(maxRating - minRating)
2517             * (h-vMargin-squareSize/8-1) + vMargin;
2518         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2519         if(strstr(seekAdList[i], " u ")) color = 1;
2520         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2521            !strstr(seekAdList[i], "bullet") &&
2522            !strstr(seekAdList[i], "blitz") &&
2523            !strstr(seekAdList[i], "standard") ) color = 2;
2524         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2525         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2526 }
2527
2528 void
2529 PlotSingleSeekAd (int i)
2530 {
2531         PlotSeekAd(i);
2532 }
2533
2534 void
2535 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2536 {
2537         char buf[MSG_SIZ], *ext = "";
2538         VariantClass v = StringToVariant(type);
2539         if(strstr(type, "wild")) {
2540             ext = type + 4; // append wild number
2541             if(v == VariantFischeRandom) type = "chess960"; else
2542             if(v == VariantLoadable) type = "setup"; else
2543             type = VariantName(v);
2544         }
2545         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2546         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2547             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2548             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2549             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2550             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2551             seekNrList[nrOfSeekAds] = nr;
2552             zList[nrOfSeekAds] = 0;
2553             seekAdList[nrOfSeekAds++] = StrSave(buf);
2554             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2555         }
2556 }
2557
2558 void
2559 EraseSeekDot (int i)
2560 {
2561     int x = xList[i], y = yList[i], d=squareSize/4, k;
2562     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2563     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2564     // now replot every dot that overlapped
2565     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2566         int xx = xList[k], yy = yList[k];
2567         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2568             DrawSeekDot(xx, yy, colorList[k]);
2569     }
2570 }
2571
2572 void
2573 RemoveSeekAd (int nr)
2574 {
2575         int i;
2576         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2577             EraseSeekDot(i);
2578             if(seekAdList[i]) free(seekAdList[i]);
2579             seekAdList[i] = seekAdList[--nrOfSeekAds];
2580             seekNrList[i] = seekNrList[nrOfSeekAds];
2581             ratingList[i] = ratingList[nrOfSeekAds];
2582             colorList[i]  = colorList[nrOfSeekAds];
2583             tcList[i] = tcList[nrOfSeekAds];
2584             xList[i]  = xList[nrOfSeekAds];
2585             yList[i]  = yList[nrOfSeekAds];
2586             zList[i]  = zList[nrOfSeekAds];
2587             seekAdList[nrOfSeekAds] = NULL;
2588             break;
2589         }
2590 }
2591
2592 Boolean
2593 MatchSoughtLine (char *line)
2594 {
2595     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2596     int nr, base, inc, u=0; char dummy;
2597
2598     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2599        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2600        (u=1) &&
2601        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2602         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2603         // match: compact and save the line
2604         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2605         return TRUE;
2606     }
2607     return FALSE;
2608 }
2609
2610 int
2611 DrawSeekGraph ()
2612 {
2613     int i;
2614     if(!seekGraphUp) return FALSE;
2615     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2616     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2617
2618     DrawSeekBackground(0, 0, w, h);
2619     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2620     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2621     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2622         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2623         yy = h-1-yy;
2624         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2625         if(i%500 == 0) {
2626             char buf[MSG_SIZ];
2627             snprintf(buf, MSG_SIZ, "%d", i);
2628             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2629         }
2630     }
2631     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2632     for(i=1; i<100; i+=(i<10?1:5)) {
2633         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2634         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2635         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2636             char buf[MSG_SIZ];
2637             snprintf(buf, MSG_SIZ, "%d", i);
2638             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2639         }
2640     }
2641     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2642     return TRUE;
2643 }
2644
2645 int
2646 SeekGraphClick (ClickType click, int x, int y, int moving)
2647 {
2648     static int lastDown = 0, displayed = 0, lastSecond;
2649     if(y < 0) return FALSE;
2650     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2651         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2652         if(!seekGraphUp) return FALSE;
2653         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2654         DrawPosition(TRUE, NULL);
2655         return TRUE;
2656     }
2657     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2658         if(click == Release || moving) return FALSE;
2659         nrOfSeekAds = 0;
2660         soughtPending = TRUE;
2661         SendToICS(ics_prefix);
2662         SendToICS("sought\n"); // should this be "sought all"?
2663     } else { // issue challenge based on clicked ad
2664         int dist = 10000; int i, closest = 0, second = 0;
2665         for(i=0; i<nrOfSeekAds; i++) {
2666             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2667             if(d < dist) { dist = d; closest = i; }
2668             second += (d - zList[i] < 120); // count in-range ads
2669             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2670         }
2671         if(dist < 120) {
2672             char buf[MSG_SIZ];
2673             second = (second > 1);
2674             if(displayed != closest || second != lastSecond) {
2675                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2676                 lastSecond = second; displayed = closest;
2677             }
2678             if(click == Press) {
2679                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2680                 lastDown = closest;
2681                 return TRUE;
2682             } // on press 'hit', only show info
2683             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2684             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2685             SendToICS(ics_prefix);
2686             SendToICS(buf);
2687             return TRUE; // let incoming board of started game pop down the graph
2688         } else if(click == Release) { // release 'miss' is ignored
2689             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2690             if(moving == 2) { // right up-click
2691                 nrOfSeekAds = 0; // refresh graph
2692                 soughtPending = TRUE;
2693                 SendToICS(ics_prefix);
2694                 SendToICS("sought\n"); // should this be "sought all"?
2695             }
2696             return TRUE;
2697         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2698         // press miss or release hit 'pop down' seek graph
2699         seekGraphUp = FALSE;
2700         DrawPosition(TRUE, NULL);
2701     }
2702     return TRUE;
2703 }
2704
2705 void
2706 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2707 {
2708 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2709 #define STARTED_NONE 0
2710 #define STARTED_MOVES 1
2711 #define STARTED_BOARD 2
2712 #define STARTED_OBSERVE 3
2713 #define STARTED_HOLDINGS 4
2714 #define STARTED_CHATTER 5
2715 #define STARTED_COMMENT 6
2716 #define STARTED_MOVES_NOHIDE 7
2717
2718     static int started = STARTED_NONE;
2719     static char parse[20000];
2720     static int parse_pos = 0;
2721     static char buf[BUF_SIZE + 1];
2722     static int firstTime = TRUE, intfSet = FALSE;
2723     static ColorClass prevColor = ColorNormal;
2724     static int savingComment = FALSE;
2725     static int cmatch = 0; // continuation sequence match
2726     char *bp;
2727     char str[MSG_SIZ];
2728     int i, oldi;
2729     int buf_len;
2730     int next_out;
2731     int tkind;
2732     int backup;    /* [DM] For zippy color lines */
2733     char *p;
2734     char talker[MSG_SIZ]; // [HGM] chat
2735     int channel;
2736
2737     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2738
2739     if (appData.debugMode) {
2740       if (!error) {
2741         fprintf(debugFP, "<ICS: ");
2742         show_bytes(debugFP, data, count);
2743         fprintf(debugFP, "\n");
2744       }
2745     }
2746
2747     if (appData.debugMode) { int f = forwardMostMove;
2748         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2749                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2750                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2751     }
2752     if (count > 0) {
2753         /* If last read ended with a partial line that we couldn't parse,
2754            prepend it to the new read and try again. */
2755         if (leftover_len > 0) {
2756             for (i=0; i<leftover_len; i++)
2757               buf[i] = buf[leftover_start + i];
2758         }
2759
2760     /* copy new characters into the buffer */
2761     bp = buf + leftover_len;
2762     buf_len=leftover_len;
2763     for (i=0; i<count; i++)
2764     {
2765         // ignore these
2766         if (data[i] == '\r')
2767             continue;
2768
2769         // join lines split by ICS?
2770         if (!appData.noJoin)
2771         {
2772             /*
2773                 Joining just consists of finding matches against the
2774                 continuation sequence, and discarding that sequence
2775                 if found instead of copying it.  So, until a match
2776                 fails, there's nothing to do since it might be the
2777                 complete sequence, and thus, something we don't want
2778                 copied.
2779             */
2780             if (data[i] == cont_seq[cmatch])
2781             {
2782                 cmatch++;
2783                 if (cmatch == strlen(cont_seq))
2784                 {
2785                     cmatch = 0; // complete match.  just reset the counter
2786
2787                     /*
2788                         it's possible for the ICS to not include the space
2789                         at the end of the last word, making our [correct]
2790                         join operation fuse two separate words.  the server
2791                         does this when the space occurs at the width setting.
2792                     */
2793                     if (!buf_len || buf[buf_len-1] != ' ')
2794                     {
2795                         *bp++ = ' ';
2796                         buf_len++;
2797                     }
2798                 }
2799                 continue;
2800             }
2801             else if (cmatch)
2802             {
2803                 /*
2804                     match failed, so we have to copy what matched before
2805                     falling through and copying this character.  In reality,
2806                     this will only ever be just the newline character, but
2807                     it doesn't hurt to be precise.
2808                 */
2809                 strncpy(bp, cont_seq, cmatch);
2810                 bp += cmatch;
2811                 buf_len += cmatch;
2812                 cmatch = 0;
2813             }
2814         }
2815
2816         // copy this char
2817         *bp++ = data[i];
2818         buf_len++;
2819     }
2820
2821         buf[buf_len] = NULLCHAR;
2822 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2823         next_out = 0;
2824         leftover_start = 0;
2825
2826         i = 0;
2827         while (i < buf_len) {
2828             /* Deal with part of the TELNET option negotiation
2829                protocol.  We refuse to do anything beyond the
2830                defaults, except that we allow the WILL ECHO option,
2831                which ICS uses to turn off password echoing when we are
2832                directly connected to it.  We reject this option
2833                if localLineEditing mode is on (always on in xboard)
2834                and we are talking to port 23, which might be a real
2835                telnet server that will try to keep WILL ECHO on permanently.
2836              */
2837             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2838                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2839                 unsigned char option;
2840                 oldi = i;
2841                 switch ((unsigned char) buf[++i]) {
2842                   case TN_WILL:
2843                     if (appData.debugMode)
2844                       fprintf(debugFP, "\n<WILL ");
2845                     switch (option = (unsigned char) buf[++i]) {
2846                       case TN_ECHO:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "ECHO ");
2849                         /* Reply only if this is a change, according
2850                            to the protocol rules. */
2851                         if (remoteEchoOption) break;
2852                         if (appData.localLineEditing &&
2853                             atoi(appData.icsPort) == TN_PORT) {
2854                             TelnetRequest(TN_DONT, TN_ECHO);
2855                         } else {
2856                             EchoOff();
2857                             TelnetRequest(TN_DO, TN_ECHO);
2858                             remoteEchoOption = TRUE;
2859                         }
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", option);
2864                         /* Whatever this is, we don't want it. */
2865                         TelnetRequest(TN_DONT, option);
2866                         break;
2867                     }
2868                     break;
2869                   case TN_WONT:
2870                     if (appData.debugMode)
2871                       fprintf(debugFP, "\n<WONT ");
2872                     switch (option = (unsigned char) buf[++i]) {
2873                       case TN_ECHO:
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "ECHO ");
2876                         /* Reply only if this is a change, according
2877                            to the protocol rules. */
2878                         if (!remoteEchoOption) break;
2879                         EchoOn();
2880                         TelnetRequest(TN_DONT, TN_ECHO);
2881                         remoteEchoOption = FALSE;
2882                         break;
2883                       default:
2884                         if (appData.debugMode)
2885                           fprintf(debugFP, "%d ", (unsigned char) option);
2886                         /* Whatever this is, it must already be turned
2887                            off, because we never agree to turn on
2888                            anything non-default, so according to the
2889                            protocol rules, we don't reply. */
2890                         break;
2891                     }
2892                     break;
2893                   case TN_DO:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<DO ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       default:
2898                         /* Whatever this is, we refuse to do it. */
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "%d ", option);
2901                         TelnetRequest(TN_WONT, option);
2902                         break;
2903                     }
2904                     break;
2905                   case TN_DONT:
2906                     if (appData.debugMode)
2907                       fprintf(debugFP, "\n<DONT ");
2908                     switch (option = (unsigned char) buf[++i]) {
2909                       default:
2910                         if (appData.debugMode)
2911                           fprintf(debugFP, "%d ", option);
2912                         /* Whatever this is, we are already not doing
2913                            it, because we never agree to do anything
2914                            non-default, so according to the protocol
2915                            rules, we don't reply. */
2916                         break;
2917                     }
2918                     break;
2919                   case TN_IAC:
2920                     if (appData.debugMode)
2921                       fprintf(debugFP, "\n<IAC ");
2922                     /* Doubled IAC; pass it through */
2923                     i--;
2924                     break;
2925                   default:
2926                     if (appData.debugMode)
2927                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2928                     /* Drop all other telnet commands on the floor */
2929                     break;
2930                 }
2931                 if (oldi > next_out)
2932                   SendToPlayer(&buf[next_out], oldi - next_out);
2933                 if (++i > next_out)
2934                   next_out = i;
2935                 continue;
2936             }
2937
2938             /* OK, this at least will *usually* work */
2939             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2940                 loggedOn = TRUE;
2941             }
2942
2943             if (loggedOn && !intfSet) {
2944                 if (ics_type == ICS_ICC) {
2945                   snprintf(str, MSG_SIZ,
2946                           "/set-quietly interface %s\n/set-quietly style 12\n",
2947                           programVersion);
2948                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2949                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2950                 } else if (ics_type == ICS_CHESSNET) {
2951                   snprintf(str, MSG_SIZ, "/style 12\n");
2952                 } else {
2953                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2954                   strcat(str, programVersion);
2955                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2956                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2957                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2958 #ifdef WIN32
2959                   strcat(str, "$iset nohighlight 1\n");
2960 #endif
2961                   strcat(str, "$iset lock 1\n$style 12\n");
2962                 }
2963                 SendToICS(str);
2964                 NotifyFrontendLogin();
2965                 intfSet = TRUE;
2966             }
2967
2968             if (started == STARTED_COMMENT) {
2969                 /* Accumulate characters in comment */
2970                 parse[parse_pos++] = buf[i];
2971                 if (buf[i] == '\n') {
2972                     parse[parse_pos] = NULLCHAR;
2973                     if(chattingPartner>=0) {
2974                         char mess[MSG_SIZ];
2975                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2976                         OutputChatMessage(chattingPartner, mess);
2977                         chattingPartner = -1;
2978                         next_out = i+1; // [HGM] suppress printing in ICS window
2979                     } else
2980                     if(!suppressKibitz) // [HGM] kibitz
2981                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2982                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2983                         int nrDigit = 0, nrAlph = 0, j;
2984                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2985                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2986                         parse[parse_pos] = NULLCHAR;
2987                         // try to be smart: if it does not look like search info, it should go to
2988                         // ICS interaction window after all, not to engine-output window.
2989                         for(j=0; j<parse_pos; j++) { // count letters and digits
2990                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2991                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2992                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2993                         }
2994                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2995                             int depth=0; float score;
2996                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2997                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2998                                 pvInfoList[forwardMostMove-1].depth = depth;
2999                                 pvInfoList[forwardMostMove-1].score = 100*score;
3000                             }
3001                             OutputKibitz(suppressKibitz, parse);
3002                         } else {
3003                             char tmp[MSG_SIZ];
3004                             if(gameMode == IcsObserving) // restore original ICS messages
3005                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3006                             else
3007                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3008                             SendToPlayer(tmp, strlen(tmp));
3009                         }
3010                         next_out = i+1; // [HGM] suppress printing in ICS window
3011                     }
3012                     started = STARTED_NONE;
3013                 } else {
3014                     /* Don't match patterns against characters in comment */
3015                     i++;
3016                     continue;
3017                 }
3018             }
3019             if (started == STARTED_CHATTER) {
3020                 if (buf[i] != '\n') {
3021                     /* Don't match patterns against characters in chatter */
3022                     i++;
3023                     continue;
3024                 }
3025                 started = STARTED_NONE;
3026                 if(suppressKibitz) next_out = i+1;
3027             }
3028
3029             /* Kludge to deal with rcmd protocol */
3030             if (firstTime && looking_at(buf, &i, "\001*")) {
3031                 DisplayFatalError(&buf[1], 0, 1);
3032                 continue;
3033             } else {
3034                 firstTime = FALSE;
3035             }
3036
3037             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3038                 ics_type = ICS_ICC;
3039                 ics_prefix = "/";
3040                 if (appData.debugMode)
3041                   fprintf(debugFP, "ics_type %d\n", ics_type);
3042                 continue;
3043             }
3044             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3045                 ics_type = ICS_FICS;
3046                 ics_prefix = "$";
3047                 if (appData.debugMode)
3048                   fprintf(debugFP, "ics_type %d\n", ics_type);
3049                 continue;
3050             }
3051             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3052                 ics_type = ICS_CHESSNET;
3053                 ics_prefix = "/";
3054                 if (appData.debugMode)
3055                   fprintf(debugFP, "ics_type %d\n", ics_type);
3056                 continue;
3057             }
3058
3059             if (!loggedOn &&
3060                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3061                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3062                  looking_at(buf, &i, "will be \"*\""))) {
3063               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3064               continue;
3065             }
3066
3067             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3068               char buf[MSG_SIZ];
3069               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3070               DisplayIcsInteractionTitle(buf);
3071               have_set_title = TRUE;
3072             }
3073
3074             /* skip finger notes */
3075             if (started == STARTED_NONE &&
3076                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3077                  (buf[i] == '1' && buf[i+1] == '0')) &&
3078                 buf[i+2] == ':' && buf[i+3] == ' ') {
3079               started = STARTED_CHATTER;
3080               i += 3;
3081               continue;
3082             }
3083
3084             oldi = i;
3085             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3086             if(appData.seekGraph) {
3087                 if(soughtPending && MatchSoughtLine(buf+i)) {
3088                     i = strstr(buf+i, "rated") - buf;
3089                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090                     next_out = leftover_start = i;
3091                     started = STARTED_CHATTER;
3092                     suppressKibitz = TRUE;
3093                     continue;
3094                 }
3095                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3096                         && looking_at(buf, &i, "* ads displayed")) {
3097                     soughtPending = FALSE;
3098                     seekGraphUp = TRUE;
3099                     DrawSeekGraph();
3100                     continue;
3101                 }
3102                 if(appData.autoRefresh) {
3103                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3104                         int s = (ics_type == ICS_ICC); // ICC format differs
3105                         if(seekGraphUp)
3106                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3107                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3108                         looking_at(buf, &i, "*% "); // eat prompt
3109                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3110                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                         next_out = i; // suppress
3112                         continue;
3113                     }
3114                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3115                         char *p = star_match[0];
3116                         while(*p) {
3117                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3118                             while(*p && *p++ != ' '); // next
3119                         }
3120                         looking_at(buf, &i, "*% "); // eat prompt
3121                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3122                         next_out = i;
3123                         continue;
3124                     }
3125                 }
3126             }
3127
3128             /* skip formula vars */
3129             if (started == STARTED_NONE &&
3130                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3131               started = STARTED_CHATTER;
3132               i += 3;
3133               continue;
3134             }
3135
3136             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3137             if (appData.autoKibitz && started == STARTED_NONE &&
3138                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3139                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3140                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3141                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3142                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3143                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3144                         suppressKibitz = TRUE;
3145                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3146                         next_out = i;
3147                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3148                                 && (gameMode == IcsPlayingWhite)) ||
3149                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3150                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3151                             started = STARTED_CHATTER; // own kibitz we simply discard
3152                         else {
3153                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3154                             parse_pos = 0; parse[0] = NULLCHAR;
3155                             savingComment = TRUE;
3156                             suppressKibitz = gameMode != IcsObserving ? 2 :
3157                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3158                         }
3159                         continue;
3160                 } else
3161                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3162                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3163                          && atoi(star_match[0])) {
3164                     // suppress the acknowledgements of our own autoKibitz
3165                     char *p;
3166                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3168                     SendToPlayer(star_match[0], strlen(star_match[0]));
3169                     if(looking_at(buf, &i, "*% ")) // eat prompt
3170                         suppressKibitz = FALSE;
3171                     next_out = i;
3172                     continue;
3173                 }
3174             } // [HGM] kibitz: end of patch
3175
3176             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3177
3178             // [HGM] chat: intercept tells by users for which we have an open chat window
3179             channel = -1;
3180             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3181                                            looking_at(buf, &i, "* whispers:") ||
3182                                            looking_at(buf, &i, "* kibitzes:") ||
3183                                            looking_at(buf, &i, "* shouts:") ||
3184                                            looking_at(buf, &i, "* c-shouts:") ||
3185                                            looking_at(buf, &i, "--> * ") ||
3186                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3187                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3188                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3189                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3190                 int p;
3191                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3192                 chattingPartner = -1;
3193
3194                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3195                 for(p=0; p<MAX_CHAT; p++) {
3196                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3197                     talker[0] = '['; strcat(talker, "] ");
3198                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3199                     chattingPartner = p; break;
3200                     }
3201                 } else
3202                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3203                 for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("kibitzes", chatPartner[p])) {
3205                         talker[0] = '['; strcat(talker, "] ");
3206                         chattingPartner = p; break;
3207                     }
3208                 } else
3209                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3210                 for(p=0; p<MAX_CHAT; p++) {
3211                     if(!strcmp("whispers", chatPartner[p])) {
3212                         talker[0] = '['; strcat(talker, "] ");
3213                         chattingPartner = p; break;
3214                     }
3215                 } else
3216                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3217                   if(buf[i-8] == '-' && buf[i-3] == 't')
3218                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3219                     if(!strcmp("c-shouts", chatPartner[p])) {
3220                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3221                         chattingPartner = p; break;
3222                     }
3223                   }
3224                   if(chattingPartner < 0)
3225                   for(p=0; p<MAX_CHAT; p++) {
3226                     if(!strcmp("shouts", chatPartner[p])) {
3227                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3228                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3229                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3230                         chattingPartner = p; break;
3231                     }
3232                   }
3233                 }
3234                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3235                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3236                     talker[0] = 0; Colorize(ColorTell, FALSE);
3237                     chattingPartner = p; break;
3238                 }
3239                 if(chattingPartner<0) i = oldi; else {
3240                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3241                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3242                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                     started = STARTED_COMMENT;
3244                     parse_pos = 0; parse[0] = NULLCHAR;
3245                     savingComment = 3 + chattingPartner; // counts as TRUE
3246                     suppressKibitz = TRUE;
3247                     continue;
3248                 }
3249             } // [HGM] chat: end of patch
3250
3251           backup = i;
3252             if (appData.zippyTalk || appData.zippyPlay) {
3253                 /* [DM] Backup address for color zippy lines */
3254 #if ZIPPY
3255                if (loggedOn == TRUE)
3256                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3257                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3258 #endif
3259             } // [DM] 'else { ' deleted
3260                 if (
3261                     /* Regular tells and says */
3262                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3263                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3264                     looking_at(buf, &i, "* says: ") ||
3265                     /* Don't color "message" or "messages" output */
3266                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3267                     looking_at(buf, &i, "*. * at *:*: ") ||
3268                     looking_at(buf, &i, "--* (*:*): ") ||
3269                     /* Message notifications (same color as tells) */
3270                     looking_at(buf, &i, "* has left a message ") ||
3271                     looking_at(buf, &i, "* just sent you a message:\n") ||
3272                     /* Whispers and kibitzes */
3273                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3274                     looking_at(buf, &i, "* kibitzes: ") ||
3275                     /* Channel tells */
3276                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3277
3278                   if (tkind == 1 && strchr(star_match[0], ':')) {
3279                       /* Avoid "tells you:" spoofs in channels */
3280                      tkind = 3;
3281                   }
3282                   if (star_match[0][0] == NULLCHAR ||
3283                       strchr(star_match[0], ' ') ||
3284                       (tkind == 3 && strchr(star_match[1], ' '))) {
3285                     /* Reject bogus matches */
3286                     i = oldi;
3287                   } else {
3288                     if (appData.colorize) {
3289                       if (oldi > next_out) {
3290                         SendToPlayer(&buf[next_out], oldi - next_out);
3291                         next_out = oldi;
3292                       }
3293                       switch (tkind) {
3294                       case 1:
3295                         Colorize(ColorTell, FALSE);
3296                         curColor = ColorTell;
3297                         break;
3298                       case 2:
3299                         Colorize(ColorKibitz, FALSE);
3300                         curColor = ColorKibitz;
3301                         break;
3302                       case 3:
3303                         p = strrchr(star_match[1], '(');
3304                         if (p == NULL) {
3305                           p = star_match[1];
3306                         } else {
3307                           p++;
3308                         }
3309                         if (atoi(p) == 1) {
3310                           Colorize(ColorChannel1, FALSE);
3311                           curColor = ColorChannel1;
3312                         } else {
3313                           Colorize(ColorChannel, FALSE);
3314                           curColor = ColorChannel;
3315                         }
3316                         break;
3317                       case 5:
3318                         curColor = ColorNormal;
3319                         break;
3320                       }
3321                     }
3322                     if (started == STARTED_NONE && appData.autoComment &&
3323                         (gameMode == IcsObserving ||
3324                          gameMode == IcsPlayingWhite ||
3325                          gameMode == IcsPlayingBlack)) {
3326                       parse_pos = i - oldi;
3327                       memcpy(parse, &buf[oldi], parse_pos);
3328                       parse[parse_pos] = NULLCHAR;
3329                       started = STARTED_COMMENT;
3330                       savingComment = TRUE;
3331                     } else {
3332                       started = STARTED_CHATTER;
3333                       savingComment = FALSE;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                   }
3338                 }
3339
3340                 if (looking_at(buf, &i, "* s-shouts: ") ||
3341                     looking_at(buf, &i, "* c-shouts: ")) {
3342                     if (appData.colorize) {
3343                         if (oldi > next_out) {
3344                             SendToPlayer(&buf[next_out], oldi - next_out);
3345                             next_out = oldi;
3346                         }
3347                         Colorize(ColorSShout, FALSE);
3348                         curColor = ColorSShout;
3349                     }
3350                     loggedOn = TRUE;
3351                     started = STARTED_CHATTER;
3352                     continue;
3353                 }
3354
3355                 if (looking_at(buf, &i, "--->")) {
3356                     loggedOn = TRUE;
3357                     continue;
3358                 }
3359
3360                 if (looking_at(buf, &i, "* shouts: ") ||
3361                     looking_at(buf, &i, "--> ")) {
3362                     if (appData.colorize) {
3363                         if (oldi > next_out) {
3364                             SendToPlayer(&buf[next_out], oldi - next_out);
3365                             next_out = oldi;
3366                         }
3367                         Colorize(ColorShout, FALSE);
3368                         curColor = ColorShout;
3369                     }
3370                     loggedOn = TRUE;
3371                     started = STARTED_CHATTER;
3372                     continue;
3373                 }
3374
3375                 if (looking_at( buf, &i, "Challenge:")) {
3376                     if (appData.colorize) {
3377                         if (oldi > next_out) {
3378                             SendToPlayer(&buf[next_out], oldi - next_out);
3379                             next_out = oldi;
3380                         }
3381                         Colorize(ColorChallenge, FALSE);
3382                         curColor = ColorChallenge;
3383                     }
3384                     loggedOn = TRUE;
3385                     continue;
3386                 }
3387
3388                 if (looking_at(buf, &i, "* offers you") ||
3389                     looking_at(buf, &i, "* offers to be") ||
3390                     looking_at(buf, &i, "* would like to") ||
3391                     looking_at(buf, &i, "* requests to") ||
3392                     looking_at(buf, &i, "Your opponent offers") ||
3393                     looking_at(buf, &i, "Your opponent requests")) {
3394
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorRequest, FALSE);
3401                         curColor = ColorRequest;
3402                     }
3403                     continue;
3404                 }
3405
3406                 if (looking_at(buf, &i, "* (*) seeking")) {
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorSeek, FALSE);
3413                         curColor = ColorSeek;
3414                     }
3415                     continue;
3416             }
3417
3418           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3419
3420             if (looking_at(buf, &i, "\\   ")) {
3421                 if (prevColor != ColorNormal) {
3422                     if (oldi > next_out) {
3423                         SendToPlayer(&buf[next_out], oldi - next_out);
3424                         next_out = oldi;
3425                     }
3426                     Colorize(prevColor, TRUE);
3427                     curColor = prevColor;
3428                 }
3429                 if (savingComment) {
3430                     parse_pos = i - oldi;
3431                     memcpy(parse, &buf[oldi], parse_pos);
3432                     parse[parse_pos] = NULLCHAR;
3433                     started = STARTED_COMMENT;
3434                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3435                         chattingPartner = savingComment - 3; // kludge to remember the box
3436                 } else {
3437                     started = STARTED_CHATTER;
3438                 }
3439                 continue;
3440             }
3441
3442             if (looking_at(buf, &i, "Black Strength :") ||
3443                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3444                 looking_at(buf, &i, "<10>") ||
3445                 looking_at(buf, &i, "#@#")) {
3446                 /* Wrong board style */
3447                 loggedOn = TRUE;
3448                 SendToICS(ics_prefix);
3449                 SendToICS("set style 12\n");
3450                 SendToICS(ics_prefix);
3451                 SendToICS("refresh\n");
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i, "login:")) {
3456               if (!have_sent_ICS_logon) {
3457                 if(ICSInitScript())
3458                   have_sent_ICS_logon = 1;
3459                 else // no init script was found
3460                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3461               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3462                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3463               }
3464                 continue;
3465             }
3466
3467             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3468                 (looking_at(buf, &i, "\n<12> ") ||
3469                  looking_at(buf, &i, "<12> "))) {
3470                 loggedOn = TRUE;
3471                 if (oldi > next_out) {
3472                     SendToPlayer(&buf[next_out], oldi - next_out);
3473                 }
3474                 next_out = i;
3475                 started = STARTED_BOARD;
3476                 parse_pos = 0;
3477                 continue;
3478             }
3479
3480             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3481                 looking_at(buf, &i, "<b1> ")) {
3482                 if (oldi > next_out) {
3483                     SendToPlayer(&buf[next_out], oldi - next_out);
3484                 }
3485                 next_out = i;
3486                 started = STARTED_HOLDINGS;
3487                 parse_pos = 0;
3488                 continue;
3489             }
3490
3491             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3492                 loggedOn = TRUE;
3493                 /* Header for a move list -- first line */
3494
3495                 switch (ics_getting_history) {
3496                   case H_FALSE:
3497                     switch (gameMode) {
3498                       case IcsIdle:
3499                       case BeginningOfGame:
3500                         /* User typed "moves" or "oldmoves" while we
3501                            were idle.  Pretend we asked for these
3502                            moves and soak them up so user can step
3503                            through them and/or save them.
3504                            */
3505                         Reset(FALSE, TRUE);
3506                         gameMode = IcsObserving;
3507                         ModeHighlight();
3508                         ics_gamenum = -1;
3509                         ics_getting_history = H_GOT_UNREQ_HEADER;
3510                         break;
3511                       case EditGame: /*?*/
3512                       case EditPosition: /*?*/
3513                         /* Should above feature work in these modes too? */
3514                         /* For now it doesn't */
3515                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3516                         break;
3517                       default:
3518                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3519                         break;
3520                     }
3521                     break;
3522                   case H_REQUESTED:
3523                     /* Is this the right one? */
3524                     if (gameInfo.white && gameInfo.black &&
3525                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3526                         strcmp(gameInfo.black, star_match[2]) == 0) {
3527                         /* All is well */
3528                         ics_getting_history = H_GOT_REQ_HEADER;
3529                     }
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                   case H_GOT_UNREQ_HEADER:
3533                   case H_GOT_UNWANTED_HEADER:
3534                   case H_GETTING_MOVES:
3535                     /* Should not happen */
3536                     DisplayError(_("Error gathering move list: two headers"), 0);
3537                     ics_getting_history = H_FALSE;
3538                     break;
3539                 }
3540
3541                 /* Save player ratings into gameInfo if needed */
3542                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3543                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3544                     (gameInfo.whiteRating == -1 ||
3545                      gameInfo.blackRating == -1)) {
3546
3547                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3548                     gameInfo.blackRating = string_to_rating(star_match[3]);
3549                     if (appData.debugMode)
3550                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3551                               gameInfo.whiteRating, gameInfo.blackRating);
3552                 }
3553                 continue;
3554             }
3555
3556             if (looking_at(buf, &i,
3557               "* * match, initial time: * minute*, increment: * second")) {
3558                 /* Header for a move list -- second line */
3559                 /* Initial board will follow if this is a wild game */
3560                 if (gameInfo.event != NULL) free(gameInfo.event);
3561                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3562                 gameInfo.event = StrSave(str);
3563                 /* [HGM] we switched variant. Translate boards if needed. */
3564                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "Move  ")) {
3569                 /* Beginning of a move list */
3570                 switch (ics_getting_history) {
3571                   case H_FALSE:
3572                     /* Normally should not happen */
3573                     /* Maybe user hit reset while we were parsing */
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Happens if we are ignoring a move list that is not
3577                      * the one we just requested.  Common if the user
3578                      * tries to observe two games without turning off
3579                      * getMoveList */
3580                     break;
3581                   case H_GETTING_MOVES:
3582                     /* Should not happen */
3583                     DisplayError(_("Error gathering move list: nested"), 0);
3584                     ics_getting_history = H_FALSE;
3585                     break;
3586                   case H_GOT_REQ_HEADER:
3587                     ics_getting_history = H_GETTING_MOVES;
3588                     started = STARTED_MOVES;
3589                     parse_pos = 0;
3590                     if (oldi > next_out) {
3591                         SendToPlayer(&buf[next_out], oldi - next_out);
3592                     }
3593                     break;
3594                   case H_GOT_UNREQ_HEADER:
3595                     ics_getting_history = H_GETTING_MOVES;
3596                     started = STARTED_MOVES_NOHIDE;
3597                     parse_pos = 0;
3598                     break;
3599                   case H_GOT_UNWANTED_HEADER:
3600                     ics_getting_history = H_FALSE;
3601                     break;
3602                 }
3603                 continue;
3604             }
3605
3606             if (looking_at(buf, &i, "% ") ||
3607                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3608                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3609                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3610                     soughtPending = FALSE;
3611                     seekGraphUp = TRUE;
3612                     DrawSeekGraph();
3613                 }
3614                 if(suppressKibitz) next_out = i;
3615                 savingComment = FALSE;
3616                 suppressKibitz = 0;
3617                 switch (started) {
3618                   case STARTED_MOVES:
3619                   case STARTED_MOVES_NOHIDE:
3620                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3621                     parse[parse_pos + i - oldi] = NULLCHAR;
3622                     ParseGameHistory(parse);
3623 #if ZIPPY
3624                     if (appData.zippyPlay && first.initDone) {
3625                         FeedMovesToProgram(&first, forwardMostMove);
3626                         if (gameMode == IcsPlayingWhite) {
3627                             if (WhiteOnMove(forwardMostMove)) {
3628                                 if (first.sendTime) {
3629                                   if (first.useColors) {
3630                                     SendToProgram("black\n", &first);
3631                                   }
3632                                   SendTimeRemaining(&first, TRUE);
3633                                 }
3634                                 if (first.useColors) {
3635                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3636                                 }
3637                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3638                                 first.maybeThinking = TRUE;
3639                             } else {
3640                                 if (first.usePlayother) {
3641                                   if (first.sendTime) {
3642                                     SendTimeRemaining(&first, TRUE);
3643                                   }
3644                                   SendToProgram("playother\n", &first);
3645                                   firstMove = FALSE;
3646                                 } else {
3647                                   firstMove = TRUE;
3648                                 }
3649                             }
3650                         } else if (gameMode == IcsPlayingBlack) {
3651                             if (!WhiteOnMove(forwardMostMove)) {
3652                                 if (first.sendTime) {
3653                                   if (first.useColors) {
3654                                     SendToProgram("white\n", &first);
3655                                   }
3656                                   SendTimeRemaining(&first, FALSE);
3657                                 }
3658                                 if (first.useColors) {
3659                                   SendToProgram("black\n", &first);
3660                                 }
3661                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3662                                 first.maybeThinking = TRUE;
3663                             } else {
3664                                 if (first.usePlayother) {
3665                                   if (first.sendTime) {
3666                                     SendTimeRemaining(&first, FALSE);
3667                                   }
3668                                   SendToProgram("playother\n", &first);
3669                                   firstMove = FALSE;
3670                                 } else {
3671                                   firstMove = TRUE;
3672                                 }
3673                             }
3674                         }
3675                     }
3676 #endif
3677                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3678                         /* Moves came from oldmoves or moves command
3679                            while we weren't doing anything else.
3680                            */
3681                         currentMove = forwardMostMove;
3682                         ClearHighlights();/*!!could figure this out*/
3683                         flipView = appData.flipView;
3684                         DrawPosition(TRUE, boards[currentMove]);
3685                         DisplayBothClocks();
3686                         snprintf(str, MSG_SIZ, "%s %s %s",
3687                                 gameInfo.white, _("vs."),  gameInfo.black);
3688                         DisplayTitle(str);
3689                         gameMode = IcsIdle;
3690                     } else {
3691                         /* Moves were history of an active game */
3692                         if (gameInfo.resultDetails != NULL) {
3693                             free(gameInfo.resultDetails);
3694                             gameInfo.resultDetails = NULL;
3695                         }
3696                     }
3697                     HistorySet(parseList, backwardMostMove,
3698                                forwardMostMove, currentMove-1);
3699                     DisplayMove(currentMove - 1);
3700                     if (started == STARTED_MOVES) next_out = i;
3701                     started = STARTED_NONE;
3702                     ics_getting_history = H_FALSE;
3703                     break;
3704
3705                   case STARTED_OBSERVE:
3706                     started = STARTED_NONE;
3707                     SendToICS(ics_prefix);
3708                     SendToICS("refresh\n");
3709                     break;
3710
3711                   default:
3712                     break;
3713                 }
3714                 if(bookHit) { // [HGM] book: simulate book reply
3715                     static char bookMove[MSG_SIZ]; // a bit generous?
3716
3717                     programStats.nodes = programStats.depth = programStats.time =
3718                     programStats.score = programStats.got_only_move = 0;
3719                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3720
3721                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3722                     strcat(bookMove, bookHit);
3723                     HandleMachineMove(bookMove, &first);
3724                 }
3725                 continue;
3726             }
3727
3728             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3729                  started == STARTED_HOLDINGS ||
3730                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3731                 /* Accumulate characters in move list or board */
3732                 parse[parse_pos++] = buf[i];
3733             }
3734
3735             /* Start of game messages.  Mostly we detect start of game
3736                when the first board image arrives.  On some versions
3737                of the ICS, though, we need to do a "refresh" after starting
3738                to observe in order to get the current board right away. */
3739             if (looking_at(buf, &i, "Adding game * to observation list")) {
3740                 started = STARTED_OBSERVE;
3741                 continue;
3742             }
3743
3744             /* Handle auto-observe */
3745             if (appData.autoObserve &&
3746                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3747                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3748                 char *player;
3749                 /* Choose the player that was highlighted, if any. */
3750                 if (star_match[0][0] == '\033' ||
3751                     star_match[1][0] != '\033') {
3752                     player = star_match[0];
3753                 } else {
3754                     player = star_match[2];
3755                 }
3756                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3757                         ics_prefix, StripHighlightAndTitle(player));
3758                 SendToICS(str);
3759
3760                 /* Save ratings from notify string */
3761                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3762                 player1Rating = string_to_rating(star_match[1]);
3763                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3764                 player2Rating = string_to_rating(star_match[3]);
3765
3766                 if (appData.debugMode)
3767                   fprintf(debugFP,
3768                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3769                           player1Name, player1Rating,
3770                           player2Name, player2Rating);
3771
3772                 continue;
3773             }
3774
3775             /* Deal with automatic examine mode after a game,
3776                and with IcsObserving -> IcsExamining transition */
3777             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3778                 looking_at(buf, &i, "has made you an examiner of game *")) {
3779
3780                 int gamenum = atoi(star_match[0]);
3781                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3782                     gamenum == ics_gamenum) {
3783                     /* We were already playing or observing this game;
3784                        no need to refetch history */
3785                     gameMode = IcsExamining;
3786                     if (pausing) {
3787                         pauseExamForwardMostMove = forwardMostMove;
3788                     } else if (currentMove < forwardMostMove) {
3789                         ForwardInner(forwardMostMove);
3790                     }
3791                 } else {
3792                     /* I don't think this case really can happen */
3793                     SendToICS(ics_prefix);
3794                     SendToICS("refresh\n");
3795                 }
3796                 continue;
3797             }
3798
3799             /* Error messages */
3800 //          if (ics_user_moved) {
3801             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3802                 if (looking_at(buf, &i, "Illegal move") ||
3803                     looking_at(buf, &i, "Not a legal move") ||
3804                     looking_at(buf, &i, "Your king is in check") ||
3805                     looking_at(buf, &i, "It isn't your turn") ||
3806                     looking_at(buf, &i, "It is not your move")) {
3807                     /* Illegal move */
3808                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3809                         currentMove = forwardMostMove-1;
3810                         DisplayMove(currentMove - 1); /* before DMError */
3811                         DrawPosition(FALSE, boards[currentMove]);
3812                         SwitchClocks(forwardMostMove-1); // [HGM] race
3813                         DisplayBothClocks();
3814                     }
3815                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3816                     ics_user_moved = 0;
3817                     continue;
3818                 }
3819             }
3820
3821             if (looking_at(buf, &i, "still have time") ||
3822                 looking_at(buf, &i, "not out of time") ||
3823                 looking_at(buf, &i, "either player is out of time") ||
3824                 looking_at(buf, &i, "has timeseal; checking")) {
3825                 /* We must have called his flag a little too soon */
3826                 whiteFlag = blackFlag = FALSE;
3827                 continue;
3828             }
3829
3830             if (looking_at(buf, &i, "added * seconds to") ||
3831                 looking_at(buf, &i, "seconds were added to")) {
3832                 /* Update the clocks */
3833                 SendToICS(ics_prefix);
3834                 SendToICS("refresh\n");
3835                 continue;
3836             }
3837
3838             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3839                 ics_clock_paused = TRUE;
3840                 StopClocks();
3841                 continue;
3842             }
3843
3844             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3845                 ics_clock_paused = FALSE;
3846                 StartClocks();
3847                 continue;
3848             }
3849
3850             /* Grab player ratings from the Creating: message.
3851                Note we have to check for the special case when
3852                the ICS inserts things like [white] or [black]. */
3853             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3854                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3855                 /* star_matches:
3856                    0    player 1 name (not necessarily white)
3857                    1    player 1 rating
3858                    2    empty, white, or black (IGNORED)
3859                    3    player 2 name (not necessarily black)
3860                    4    player 2 rating
3861
3862                    The names/ratings are sorted out when the game
3863                    actually starts (below).
3864                 */
3865                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3866                 player1Rating = string_to_rating(star_match[1]);
3867                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3868                 player2Rating = string_to_rating(star_match[4]);
3869
3870                 if (appData.debugMode)
3871                   fprintf(debugFP,
3872                           "Ratings from 'Creating:' %s %d, %s %d\n",
3873                           player1Name, player1Rating,
3874                           player2Name, player2Rating);
3875
3876                 continue;
3877             }
3878
3879             /* Improved generic start/end-of-game messages */
3880             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3881                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3882                 /* If tkind == 0: */
3883                 /* star_match[0] is the game number */
3884                 /*           [1] is the white player's name */
3885                 /*           [2] is the black player's name */
3886                 /* For end-of-game: */
3887                 /*           [3] is the reason for the game end */
3888                 /*           [4] is a PGN end game-token, preceded by " " */
3889                 /* For start-of-game: */
3890                 /*           [3] begins with "Creating" or "Continuing" */
3891                 /*           [4] is " *" or empty (don't care). */
3892                 int gamenum = atoi(star_match[0]);
3893                 char *whitename, *blackname, *why, *endtoken;
3894                 ChessMove endtype = EndOfFile;
3895
3896                 if (tkind == 0) {
3897                   whitename = star_match[1];
3898                   blackname = star_match[2];
3899                   why = star_match[3];
3900                   endtoken = star_match[4];
3901                 } else {
3902                   whitename = star_match[1];
3903                   blackname = star_match[3];
3904                   why = star_match[5];
3905                   endtoken = star_match[6];
3906                 }
3907
3908                 /* Game start messages */
3909                 if (strncmp(why, "Creating ", 9) == 0 ||
3910                     strncmp(why, "Continuing ", 11) == 0) {
3911                     gs_gamenum = gamenum;
3912                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3913                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3914                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3915 #if ZIPPY
3916                     if (appData.zippyPlay) {
3917                         ZippyGameStart(whitename, blackname);
3918                     }
3919 #endif /*ZIPPY*/
3920                     partnerBoardValid = FALSE; // [HGM] bughouse
3921                     continue;
3922                 }
3923
3924                 /* Game end messages */
3925                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3926                     ics_gamenum != gamenum) {
3927                     continue;
3928                 }
3929                 while (endtoken[0] == ' ') endtoken++;
3930                 switch (endtoken[0]) {
3931                   case '*':
3932                   default:
3933                     endtype = GameUnfinished;
3934                     break;
3935                   case '0':
3936                     endtype = BlackWins;
3937                     break;
3938                   case '1':
3939                     if (endtoken[1] == '/')
3940                       endtype = GameIsDrawn;
3941                     else
3942                       endtype = WhiteWins;
3943                     break;
3944                 }
3945                 GameEnds(endtype, why, GE_ICS);
3946 #if ZIPPY
3947                 if (appData.zippyPlay && first.initDone) {
3948                     ZippyGameEnd(endtype, why);
3949                     if (first.pr == NoProc) {
3950                       /* Start the next process early so that we'll
3951                          be ready for the next challenge */
3952                       StartChessProgram(&first);
3953                     }
3954                     /* Send "new" early, in case this command takes
3955                        a long time to finish, so that we'll be ready
3956                        for the next challenge. */
3957                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3958                     Reset(TRUE, TRUE);
3959                 }
3960 #endif /*ZIPPY*/
3961                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "Removing game * from observation") ||
3966                 looking_at(buf, &i, "no longer observing game *") ||
3967                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3968                 if (gameMode == IcsObserving &&
3969                     atoi(star_match[0]) == ics_gamenum)
3970                   {
3971                       /* icsEngineAnalyze */
3972                       if (appData.icsEngineAnalyze) {
3973                             ExitAnalyzeMode();
3974                             ModeHighlight();
3975                       }
3976                       StopClocks();
3977                       gameMode = IcsIdle;
3978                       ics_gamenum = -1;
3979                       ics_user_moved = FALSE;
3980                   }
3981                 continue;
3982             }
3983
3984             if (looking_at(buf, &i, "no longer examining game *")) {
3985                 if (gameMode == IcsExamining &&
3986                     atoi(star_match[0]) == ics_gamenum)
3987                   {
3988                       gameMode = IcsIdle;
3989                       ics_gamenum = -1;
3990                       ics_user_moved = FALSE;
3991                   }
3992                 continue;
3993             }
3994
3995             /* Advance leftover_start past any newlines we find,
3996                so only partial lines can get reparsed */
3997             if (looking_at(buf, &i, "\n")) {
3998                 prevColor = curColor;
3999                 if (curColor != ColorNormal) {
4000                     if (oldi > next_out) {
4001                         SendToPlayer(&buf[next_out], oldi - next_out);
4002                         next_out = oldi;
4003                     }
4004                     Colorize(ColorNormal, FALSE);
4005                     curColor = ColorNormal;
4006                 }
4007                 if (started == STARTED_BOARD) {
4008                     started = STARTED_NONE;
4009                     parse[parse_pos] = NULLCHAR;
4010                     ParseBoard12(parse);
4011                     ics_user_moved = 0;
4012
4013                     /* Send premove here */
4014                     if (appData.premove) {
4015                       char str[MSG_SIZ];
4016                       if (currentMove == 0 &&
4017                           gameMode == IcsPlayingWhite &&
4018                           appData.premoveWhite) {
4019                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4020                         if (appData.debugMode)
4021                           fprintf(debugFP, "Sending premove:\n");
4022                         SendToICS(str);
4023                       } else if (currentMove == 1 &&
4024                                  gameMode == IcsPlayingBlack &&
4025                                  appData.premoveBlack) {
4026                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4027                         if (appData.debugMode)
4028                           fprintf(debugFP, "Sending premove:\n");
4029                         SendToICS(str);
4030                       } else if (gotPremove) {
4031                         gotPremove = 0;
4032                         ClearPremoveHighlights();
4033                         if (appData.debugMode)
4034                           fprintf(debugFP, "Sending premove:\n");
4035                           UserMoveEvent(premoveFromX, premoveFromY,
4036                                         premoveToX, premoveToY,
4037                                         premovePromoChar);
4038                       }
4039                     }
4040
4041                     /* Usually suppress following prompt */
4042                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4043                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4044                         if (looking_at(buf, &i, "*% ")) {
4045                             savingComment = FALSE;
4046                             suppressKibitz = 0;
4047                         }
4048                     }
4049                     next_out = i;
4050                 } else if (started == STARTED_HOLDINGS) {
4051                     int gamenum;
4052                     char new_piece[MSG_SIZ];
4053                     started = STARTED_NONE;
4054                     parse[parse_pos] = NULLCHAR;
4055                     if (appData.debugMode)
4056                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4057                                                         parse, currentMove);
4058                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4059                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4060                         if (gameInfo.variant == VariantNormal) {
4061                           /* [HGM] We seem to switch variant during a game!
4062                            * Presumably no holdings were displayed, so we have
4063                            * to move the position two files to the right to
4064                            * create room for them!
4065                            */
4066                           VariantClass newVariant;
4067                           switch(gameInfo.boardWidth) { // base guess on board width
4068                                 case 9:  newVariant = VariantShogi; break;
4069                                 case 10: newVariant = VariantGreat; break;
4070                                 default: newVariant = VariantCrazyhouse; break;
4071                           }
4072                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4073                           /* Get a move list just to see the header, which
4074                              will tell us whether this is really bug or zh */
4075                           if (ics_getting_history == H_FALSE) {
4076                             ics_getting_history = H_REQUESTED;
4077                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4078                             SendToICS(str);
4079                           }
4080                         }
4081                         new_piece[0] = NULLCHAR;
4082                         sscanf(parse, "game %d white [%s black [%s <- %s",
4083                                &gamenum, white_holding, black_holding,
4084                                new_piece);
4085                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4086                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4087                         /* [HGM] copy holdings to board holdings area */
4088                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4089                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4090                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4091 #if ZIPPY
4092                         if (appData.zippyPlay && first.initDone) {
4093                             ZippyHoldings(white_holding, black_holding,
4094                                           new_piece);
4095                         }
4096 #endif /*ZIPPY*/
4097                         if (tinyLayout || smallLayout) {
4098                             char wh[16], bh[16];
4099                             PackHolding(wh, white_holding);
4100                             PackHolding(bh, black_holding);
4101                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4102                                     gameInfo.white, gameInfo.black);
4103                         } else {
4104                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4105                                     gameInfo.white, white_holding, _("vs."),
4106                                     gameInfo.black, black_holding);
4107                         }
4108                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4109                         DrawPosition(FALSE, boards[currentMove]);
4110                         DisplayTitle(str);
4111                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4112                         sscanf(parse, "game %d white [%s black [%s <- %s",
4113                                &gamenum, white_holding, black_holding,
4114                                new_piece);
4115                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4116                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4117                         /* [HGM] copy holdings to partner-board holdings area */
4118                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4119                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4120                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4121                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4122                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4123                       }
4124                     }
4125                     /* Suppress following prompt */
4126                     if (looking_at(buf, &i, "*% ")) {
4127                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4128                         savingComment = FALSE;
4129                         suppressKibitz = 0;
4130                     }
4131                     next_out = i;
4132                 }
4133                 continue;
4134             }
4135
4136             i++;                /* skip unparsed character and loop back */
4137         }
4138
4139         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4140 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4141 //          SendToPlayer(&buf[next_out], i - next_out);
4142             started != STARTED_HOLDINGS && leftover_start > next_out) {
4143             SendToPlayer(&buf[next_out], leftover_start - next_out);
4144             next_out = i;
4145         }
4146
4147         leftover_len = buf_len - leftover_start;
4148         /* if buffer ends with something we couldn't parse,
4149            reparse it after appending the next read */
4150
4151     } else if (count == 0) {
4152         RemoveInputSource(isr);
4153         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4154     } else {
4155         DisplayFatalError(_("Error reading from ICS"), error, 1);
4156     }
4157 }
4158
4159
4160 /* Board style 12 looks like this:
4161
4162    <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
4163
4164  * The "<12> " is stripped before it gets to this routine.  The two
4165  * trailing 0's (flip state and clock ticking) are later addition, and
4166  * some chess servers may not have them, or may have only the first.
4167  * Additional trailing fields may be added in the future.
4168  */
4169
4170 #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"
4171
4172 #define RELATION_OBSERVING_PLAYED    0
4173 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4174 #define RELATION_PLAYING_MYMOVE      1
4175 #define RELATION_PLAYING_NOTMYMOVE  -1
4176 #define RELATION_EXAMINING           2
4177 #define RELATION_ISOLATED_BOARD     -3
4178 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4179
4180 void
4181 ParseBoard12 (char *string)
4182 {
4183 #if ZIPPY
4184     int i, takeback;
4185     char *bookHit = NULL; // [HGM] book
4186 #endif
4187     GameMode newGameMode;
4188     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4189     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4190     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4191     char to_play, board_chars[200];
4192     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4193     char black[32], white[32];
4194     Board board;
4195     int prevMove = currentMove;
4196     int ticking = 2;
4197     ChessMove moveType;
4198     int fromX, fromY, toX, toY;
4199     char promoChar;
4200     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4201     Boolean weird = FALSE, reqFlag = FALSE;
4202
4203     fromX = fromY = toX = toY = -1;
4204
4205     newGame = FALSE;
4206
4207     if (appData.debugMode)
4208       fprintf(debugFP, _("Parsing board: %s\n"), string);
4209
4210     move_str[0] = NULLCHAR;
4211     elapsed_time[0] = NULLCHAR;
4212     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4213         int  i = 0, j;
4214         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4215             if(string[i] == ' ') { ranks++; files = 0; }
4216             else files++;
4217             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4218             i++;
4219         }
4220         for(j = 0; j <i; j++) board_chars[j] = string[j];
4221         board_chars[i] = '\0';
4222         string += i + 1;
4223     }
4224     n = sscanf(string, PATTERN, &to_play, &double_push,
4225                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4226                &gamenum, white, black, &relation, &basetime, &increment,
4227                &white_stren, &black_stren, &white_time, &black_time,
4228                &moveNum, str, elapsed_time, move_str, &ics_flip,
4229                &ticking);
4230
4231     if (n < 21) {
4232         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4233         DisplayError(str, 0);
4234         return;
4235     }
4236
4237     /* Convert the move number to internal form */
4238     moveNum = (moveNum - 1) * 2;
4239     if (to_play == 'B') moveNum++;
4240     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4241       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4242                         0, 1);
4243       return;
4244     }
4245
4246     switch (relation) {
4247       case RELATION_OBSERVING_PLAYED:
4248       case RELATION_OBSERVING_STATIC:
4249         if (gamenum == -1) {
4250             /* Old ICC buglet */
4251             relation = RELATION_OBSERVING_STATIC;
4252         }
4253         newGameMode = IcsObserving;
4254         break;
4255       case RELATION_PLAYING_MYMOVE:
4256       case RELATION_PLAYING_NOTMYMOVE:
4257         newGameMode =
4258           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4259             IcsPlayingWhite : IcsPlayingBlack;
4260         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4261         break;
4262       case RELATION_EXAMINING:
4263         newGameMode = IcsExamining;
4264         break;
4265       case RELATION_ISOLATED_BOARD:
4266       default:
4267         /* Just display this board.  If user was doing something else,
4268            we will forget about it until the next board comes. */
4269         newGameMode = IcsIdle;
4270         break;
4271       case RELATION_STARTING_POSITION:
4272         newGameMode = gameMode;
4273         break;
4274     }
4275
4276     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4277         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4278          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4279       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4280       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4281       static int lastBgGame = -1;
4282       char *toSqr;
4283       for (k = 0; k < ranks; k++) {
4284         for (j = 0; j < files; j++)
4285           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4286         if(gameInfo.holdingsWidth > 1) {
4287              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4288              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4289         }
4290       }
4291       CopyBoard(partnerBoard, board);
4292       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4293         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4294         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4295       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4296       if(toSqr = strchr(str, '-')) {
4297         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4298         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4299       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4300       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4301       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4302       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4303       if(twoBoards) {
4304           DisplayWhiteClock(white_time*fac, to_play == 'W');
4305           DisplayBlackClock(black_time*fac, to_play != 'W');
4306           activePartner = to_play;
4307           if(gamenum != lastBgGame) {
4308               char buf[MSG_SIZ];
4309               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4310               DisplayTitle(buf);
4311           }
4312           lastBgGame = gamenum;
4313           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4314                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4315       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4316                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4317       DisplayMessage(partnerStatus, "");
4318         partnerBoardValid = TRUE;
4319       return;
4320     }
4321
4322     if(appData.dualBoard && appData.bgObserve) {
4323         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4324             SendToICS(ics_prefix), SendToICS("pobserve\n");
4325         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4326             char buf[MSG_SIZ];
4327             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4328             SendToICS(buf);
4329         }
4330     }
4331
4332     /* Modify behavior for initial board display on move listing
4333        of wild games.
4334        */
4335     switch (ics_getting_history) {
4336       case H_FALSE:
4337       case H_REQUESTED:
4338         break;
4339       case H_GOT_REQ_HEADER:
4340       case H_GOT_UNREQ_HEADER:
4341         /* This is the initial position of the current game */
4342         gamenum = ics_gamenum;
4343         moveNum = 0;            /* old ICS bug workaround */
4344         if (to_play == 'B') {
4345           startedFromSetupPosition = TRUE;
4346           blackPlaysFirst = TRUE;
4347           moveNum = 1;
4348           if (forwardMostMove == 0) forwardMostMove = 1;
4349           if (backwardMostMove == 0) backwardMostMove = 1;
4350           if (currentMove == 0) currentMove = 1;
4351         }
4352         newGameMode = gameMode;
4353         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4354         break;
4355       case H_GOT_UNWANTED_HEADER:
4356         /* This is an initial board that we don't want */
4357         return;
4358       case H_GETTING_MOVES:
4359         /* Should not happen */
4360         DisplayError(_("Error gathering move list: extra board"), 0);
4361         ics_getting_history = H_FALSE;
4362         return;
4363     }
4364
4365    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4366                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4367                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4368      /* [HGM] We seem to have switched variant unexpectedly
4369       * Try to guess new variant from board size
4370       */
4371           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4372           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4373           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4374           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4375           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4376           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4377           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4378           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4379           /* Get a move list just to see the header, which
4380              will tell us whether this is really bug or zh */
4381           if (ics_getting_history == H_FALSE) {
4382             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4383             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4384             SendToICS(str);
4385           }
4386     }
4387
4388     /* Take action if this is the first board of a new game, or of a
4389        different game than is currently being displayed.  */
4390     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4391         relation == RELATION_ISOLATED_BOARD) {
4392
4393         /* Forget the old game and get the history (if any) of the new one */
4394         if (gameMode != BeginningOfGame) {
4395           Reset(TRUE, TRUE);
4396         }
4397         newGame = TRUE;
4398         if (appData.autoRaiseBoard) BoardToTop();
4399         prevMove = -3;
4400         if (gamenum == -1) {
4401             newGameMode = IcsIdle;
4402         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4403                    appData.getMoveList && !reqFlag) {
4404             /* Need to get game history */
4405             ics_getting_history = H_REQUESTED;
4406             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4407             SendToICS(str);
4408         }
4409
4410         /* Initially flip the board to have black on the bottom if playing
4411            black or if the ICS flip flag is set, but let the user change
4412            it with the Flip View button. */
4413         flipView = appData.autoFlipView ?
4414           (newGameMode == IcsPlayingBlack) || ics_flip :
4415           appData.flipView;
4416
4417         /* Done with values from previous mode; copy in new ones */
4418         gameMode = newGameMode;
4419         ModeHighlight();
4420         ics_gamenum = gamenum;
4421         if (gamenum == gs_gamenum) {
4422             int klen = strlen(gs_kind);
4423             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4424             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4425             gameInfo.event = StrSave(str);
4426         } else {
4427             gameInfo.event = StrSave("ICS game");
4428         }
4429         gameInfo.site = StrSave(appData.icsHost);
4430         gameInfo.date = PGNDate();
4431         gameInfo.round = StrSave("-");
4432         gameInfo.white = StrSave(white);
4433         gameInfo.black = StrSave(black);
4434         timeControl = basetime * 60 * 1000;
4435         timeControl_2 = 0;
4436         timeIncrement = increment * 1000;
4437         movesPerSession = 0;
4438         gameInfo.timeControl = TimeControlTagValue();
4439         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4440   if (appData.debugMode) {
4441     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4442     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4443     setbuf(debugFP, NULL);
4444   }
4445
4446         gameInfo.outOfBook = NULL;
4447
4448         /* Do we have the ratings? */
4449         if (strcmp(player1Name, white) == 0 &&
4450             strcmp(player2Name, black) == 0) {
4451             if (appData.debugMode)
4452               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4453                       player1Rating, player2Rating);
4454             gameInfo.whiteRating = player1Rating;
4455             gameInfo.blackRating = player2Rating;
4456         } else if (strcmp(player2Name, white) == 0 &&
4457                    strcmp(player1Name, black) == 0) {
4458             if (appData.debugMode)
4459               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4460                       player2Rating, player1Rating);
4461             gameInfo.whiteRating = player2Rating;
4462             gameInfo.blackRating = player1Rating;
4463         }
4464         player1Name[0] = player2Name[0] = NULLCHAR;
4465
4466         /* Silence shouts if requested */
4467         if (appData.quietPlay &&
4468             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4469             SendToICS(ics_prefix);
4470             SendToICS("set shout 0\n");
4471         }
4472     }
4473
4474     /* Deal with midgame name changes */
4475     if (!newGame) {
4476         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4477             if (gameInfo.white) free(gameInfo.white);
4478             gameInfo.white = StrSave(white);
4479         }
4480         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4481             if (gameInfo.black) free(gameInfo.black);
4482             gameInfo.black = StrSave(black);
4483         }
4484     }
4485
4486     /* Throw away game result if anything actually changes in examine mode */
4487     if (gameMode == IcsExamining && !newGame) {
4488         gameInfo.result = GameUnfinished;
4489         if (gameInfo.resultDetails != NULL) {
4490             free(gameInfo.resultDetails);
4491             gameInfo.resultDetails = NULL;
4492         }
4493     }
4494
4495     /* In pausing && IcsExamining mode, we ignore boards coming
4496        in if they are in a different variation than we are. */
4497     if (pauseExamInvalid) return;
4498     if (pausing && gameMode == IcsExamining) {
4499         if (moveNum <= pauseExamForwardMostMove) {
4500             pauseExamInvalid = TRUE;
4501             forwardMostMove = pauseExamForwardMostMove;
4502             return;
4503         }
4504     }
4505
4506   if (appData.debugMode) {
4507     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4508   }
4509     /* Parse the board */
4510     for (k = 0; k < ranks; k++) {
4511       for (j = 0; j < files; j++)
4512         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4513       if(gameInfo.holdingsWidth > 1) {
4514            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4515            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4516       }
4517     }
4518     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4519       board[5][BOARD_RGHT+1] = WhiteAngel;
4520       board[6][BOARD_RGHT+1] = WhiteMarshall;
4521       board[1][0] = BlackMarshall;
4522       board[2][0] = BlackAngel;
4523       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4524     }
4525     CopyBoard(boards[moveNum], board);
4526     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4527     if (moveNum == 0) {
4528         startedFromSetupPosition =
4529           !CompareBoards(board, initialPosition);
4530         if(startedFromSetupPosition)
4531             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4532     }
4533
4534     /* [HGM] Set castling rights. Take the outermost Rooks,
4535        to make it also work for FRC opening positions. Note that board12
4536        is really defective for later FRC positions, as it has no way to
4537        indicate which Rook can castle if they are on the same side of King.
4538        For the initial position we grant rights to the outermost Rooks,
4539        and remember thos rights, and we then copy them on positions
4540        later in an FRC game. This means WB might not recognize castlings with
4541        Rooks that have moved back to their original position as illegal,
4542        but in ICS mode that is not its job anyway.
4543     */
4544     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4545     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4546
4547         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4548             if(board[0][i] == WhiteRook) j = i;
4549         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4550         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4551             if(board[0][i] == WhiteRook) j = i;
4552         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4553         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4554             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4555         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4556         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4557             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4558         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4559
4560         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4561         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4562         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4563             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4564         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4565             if(board[BOARD_HEIGHT-1][k] == bKing)
4566                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4567         if(gameInfo.variant == VariantTwoKings) {
4568             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4569             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4570             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4571         }
4572     } else { int r;
4573         r = boards[moveNum][CASTLING][0] = initialRights[0];
4574         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4575         r = boards[moveNum][CASTLING][1] = initialRights[1];
4576         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4577         r = boards[moveNum][CASTLING][3] = initialRights[3];
4578         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4579         r = boards[moveNum][CASTLING][4] = initialRights[4];
4580         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4581         /* wildcastle kludge: always assume King has rights */
4582         r = boards[moveNum][CASTLING][2] = initialRights[2];
4583         r = boards[moveNum][CASTLING][5] = initialRights[5];
4584     }
4585     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4586     boards[moveNum][EP_STATUS] = EP_NONE;
4587     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4588     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4589     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4590
4591
4592     if (ics_getting_history == H_GOT_REQ_HEADER ||
4593         ics_getting_history == H_GOT_UNREQ_HEADER) {
4594         /* This was an initial position from a move list, not
4595            the current position */
4596         return;
4597     }
4598
4599     /* Update currentMove and known move number limits */
4600     newMove = newGame || moveNum > forwardMostMove;
4601
4602     if (newGame) {
4603         forwardMostMove = backwardMostMove = currentMove = moveNum;
4604         if (gameMode == IcsExamining && moveNum == 0) {
4605           /* Workaround for ICS limitation: we are not told the wild
4606              type when starting to examine a game.  But if we ask for
4607              the move list, the move list header will tell us */
4608             ics_getting_history = H_REQUESTED;
4609             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4610             SendToICS(str);
4611         }
4612     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4613                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4614 #if ZIPPY
4615         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4616         /* [HGM] applied this also to an engine that is silently watching        */
4617         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4618             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4619             gameInfo.variant == currentlyInitializedVariant) {
4620           takeback = forwardMostMove - moveNum;
4621           for (i = 0; i < takeback; i++) {
4622             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4623             SendToProgram("undo\n", &first);
4624           }
4625         }
4626 #endif
4627
4628         forwardMostMove = moveNum;
4629         if (!pausing || currentMove > forwardMostMove)
4630           currentMove = forwardMostMove;
4631     } else {
4632         /* New part of history that is not contiguous with old part */
4633         if (pausing && gameMode == IcsExamining) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4639 #if ZIPPY
4640             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4641                 // [HGM] when we will receive the move list we now request, it will be
4642                 // fed to the engine from the first move on. So if the engine is not
4643                 // in the initial position now, bring it there.
4644                 InitChessProgram(&first, 0);
4645             }
4646 #endif
4647             ics_getting_history = H_REQUESTED;
4648             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4649             SendToICS(str);
4650         }
4651         forwardMostMove = backwardMostMove = currentMove = moveNum;
4652     }
4653
4654     /* Update the clocks */
4655     if (strchr(elapsed_time, '.')) {
4656       /* Time is in ms */
4657       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4658       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4659     } else {
4660       /* Time is in seconds */
4661       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4662       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4663     }
4664
4665
4666 #if ZIPPY
4667     if (appData.zippyPlay && newGame &&
4668         gameMode != IcsObserving && gameMode != IcsIdle &&
4669         gameMode != IcsExamining)
4670       ZippyFirstBoard(moveNum, basetime, increment);
4671 #endif
4672
4673     /* Put the move on the move list, first converting
4674        to canonical algebraic form. */
4675     if (moveNum > 0) {
4676   if (appData.debugMode) {
4677     if (appData.debugMode) { int f = forwardMostMove;
4678         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4679                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4680                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4681     }
4682     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4683     fprintf(debugFP, "moveNum = %d\n", moveNum);
4684     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4685     setbuf(debugFP, NULL);
4686   }
4687         if (moveNum <= backwardMostMove) {
4688             /* We don't know what the board looked like before
4689                this move.  Punt. */
4690           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4691             strcat(parseList[moveNum - 1], " ");
4692             strcat(parseList[moveNum - 1], elapsed_time);
4693             moveList[moveNum - 1][0] = NULLCHAR;
4694         } else if (strcmp(move_str, "none") == 0) {
4695             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4696             /* Again, we don't know what the board looked like;
4697                this is really the start of the game. */
4698             parseList[moveNum - 1][0] = NULLCHAR;
4699             moveList[moveNum - 1][0] = NULLCHAR;
4700             backwardMostMove = moveNum;
4701             startedFromSetupPosition = TRUE;
4702             fromX = fromY = toX = toY = -1;
4703         } else {
4704           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4705           //                 So we parse the long-algebraic move string in stead of the SAN move
4706           int valid; char buf[MSG_SIZ], *prom;
4707
4708           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4709                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4710           // str looks something like "Q/a1-a2"; kill the slash
4711           if(str[1] == '/')
4712             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4713           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4714           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4715                 strcat(buf, prom); // long move lacks promo specification!
4716           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4717                 if(appData.debugMode)
4718                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4719                 safeStrCpy(move_str, buf, MSG_SIZ);
4720           }
4721           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4722                                 &fromX, &fromY, &toX, &toY, &promoChar)
4723                || ParseOneMove(buf, moveNum - 1, &moveType,
4724                                 &fromX, &fromY, &toX, &toY, &promoChar);
4725           // end of long SAN patch
4726           if (valid) {
4727             (void) CoordsToAlgebraic(boards[moveNum - 1],
4728                                      PosFlags(moveNum - 1),
4729                                      fromY, fromX, toY, toX, promoChar,
4730                                      parseList[moveNum-1]);
4731             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4732               case MT_NONE:
4733               case MT_STALEMATE:
4734               default:
4735                 break;
4736               case MT_CHECK:
4737                 if(gameInfo.variant != VariantShogi)
4738                     strcat(parseList[moveNum - 1], "+");
4739                 break;
4740               case MT_CHECKMATE:
4741               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4742                 strcat(parseList[moveNum - 1], "#");
4743                 break;
4744             }
4745             strcat(parseList[moveNum - 1], " ");
4746             strcat(parseList[moveNum - 1], elapsed_time);
4747             /* currentMoveString is set as a side-effect of ParseOneMove */
4748             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4749             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4750             strcat(moveList[moveNum - 1], "\n");
4751
4752             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4753                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4754               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4755                 ChessSquare old, new = boards[moveNum][k][j];
4756                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4757                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4758                   if(old == new) continue;
4759                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4760                   else if(new == WhiteWazir || new == BlackWazir) {
4761                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4762                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4763                       else boards[moveNum][k][j] = old; // preserve type of Gold
4764                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4765                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4766               }
4767           } else {
4768             /* Move from ICS was illegal!?  Punt. */
4769             if (appData.debugMode) {
4770               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4771               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4772             }
4773             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4774             strcat(parseList[moveNum - 1], " ");
4775             strcat(parseList[moveNum - 1], elapsed_time);
4776             moveList[moveNum - 1][0] = NULLCHAR;
4777             fromX = fromY = toX = toY = -1;
4778           }
4779         }
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4782     setbuf(debugFP, NULL);
4783   }
4784
4785 #if ZIPPY
4786         /* Send move to chess program (BEFORE animating it). */
4787         if (appData.zippyPlay && !newGame && newMove &&
4788            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4789
4790             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4791                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4792                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4793                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4794                             move_str);
4795                     DisplayError(str, 0);
4796                 } else {
4797                     if (first.sendTime) {
4798                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4799                     }
4800                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4801                     if (firstMove && !bookHit) {
4802                         firstMove = FALSE;
4803                         if (first.useColors) {
4804                           SendToProgram(gameMode == IcsPlayingWhite ?
4805                                         "white\ngo\n" :
4806                                         "black\ngo\n", &first);
4807                         } else {
4808                           SendToProgram("go\n", &first);
4809                         }
4810                         first.maybeThinking = TRUE;
4811                     }
4812                 }
4813             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4814               if (moveList[moveNum - 1][0] == NULLCHAR) {
4815                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4816                 DisplayError(str, 0);
4817               } else {
4818                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4819                 SendMoveToProgram(moveNum - 1, &first);
4820               }
4821             }
4822         }
4823 #endif
4824     }
4825
4826     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4827         /* If move comes from a remote source, animate it.  If it
4828            isn't remote, it will have already been animated. */
4829         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4830             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4831         }
4832         if (!pausing && appData.highlightLastMove) {
4833             SetHighlights(fromX, fromY, toX, toY);
4834         }
4835     }
4836
4837     /* Start the clocks */
4838     whiteFlag = blackFlag = FALSE;
4839     appData.clockMode = !(basetime == 0 && increment == 0);
4840     if (ticking == 0) {
4841       ics_clock_paused = TRUE;
4842       StopClocks();
4843     } else if (ticking == 1) {
4844       ics_clock_paused = FALSE;
4845     }
4846     if (gameMode == IcsIdle ||
4847         relation == RELATION_OBSERVING_STATIC ||
4848         relation == RELATION_EXAMINING ||
4849         ics_clock_paused)
4850       DisplayBothClocks();
4851     else
4852       StartClocks();
4853
4854     /* Display opponents and material strengths */
4855     if (gameInfo.variant != VariantBughouse &&
4856         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4857         if (tinyLayout || smallLayout) {
4858             if(gameInfo.variant == VariantNormal)
4859               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4860                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4861                     basetime, increment);
4862             else
4863               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4864                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4865                     basetime, increment, (int) gameInfo.variant);
4866         } else {
4867             if(gameInfo.variant == VariantNormal)
4868               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4869                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4870                     basetime, increment);
4871             else
4872               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4873                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4874                     basetime, increment, VariantName(gameInfo.variant));
4875         }
4876         DisplayTitle(str);
4877   if (appData.debugMode) {
4878     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4879   }
4880     }
4881
4882
4883     /* Display the board */
4884     if (!pausing && !appData.noGUI) {
4885
4886       if (appData.premove)
4887           if (!gotPremove ||
4888              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4889              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4890               ClearPremoveHighlights();
4891
4892       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4893         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4894       DrawPosition(j, boards[currentMove]);
4895
4896       DisplayMove(moveNum - 1);
4897       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4898             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4899               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4900         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4901       }
4902     }
4903
4904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4905 #if ZIPPY
4906     if(bookHit) { // [HGM] book: simulate book reply
4907         static char bookMove[MSG_SIZ]; // a bit generous?
4908
4909         programStats.nodes = programStats.depth = programStats.time =
4910         programStats.score = programStats.got_only_move = 0;
4911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4912
4913         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4914         strcat(bookMove, bookHit);
4915         HandleMachineMove(bookMove, &first);
4916     }
4917 #endif
4918 }
4919
4920 void
4921 GetMoveListEvent ()
4922 {
4923     char buf[MSG_SIZ];
4924     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4925         ics_getting_history = H_REQUESTED;
4926         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4927         SendToICS(buf);
4928     }
4929 }
4930
4931 void
4932 SendToBoth (char *msg)
4933 {   // to make it easy to keep two engines in step in dual analysis
4934     SendToProgram(msg, &first);
4935     if(second.analyzing) SendToProgram(msg, &second);
4936 }
4937
4938 void
4939 AnalysisPeriodicEvent (int force)
4940 {
4941     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4942          && !force) || !appData.periodicUpdates)
4943       return;
4944
4945     /* Send . command to Crafty to collect stats */
4946     SendToBoth(".\n");
4947
4948     /* Don't send another until we get a response (this makes
4949        us stop sending to old Crafty's which don't understand
4950        the "." command (sending illegal cmds resets node count & time,
4951        which looks bad)) */
4952     programStats.ok_to_send = 0;
4953 }
4954
4955 void
4956 ics_update_width (int new_width)
4957 {
4958         ics_printf("set width %d\n", new_width);
4959 }
4960
4961 void
4962 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4963 {
4964     char buf[MSG_SIZ];
4965
4966     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4967         // null move in variant where engine does not understand it (for analysis purposes)
4968         SendBoard(cps, moveNum + 1); // send position after move in stead.
4969         return;
4970     }
4971     if (cps->useUsermove) {
4972       SendToProgram("usermove ", cps);
4973     }
4974     if (cps->useSAN) {
4975       char *space;
4976       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4977         int len = space - parseList[moveNum];
4978         memcpy(buf, parseList[moveNum], len);
4979         buf[len++] = '\n';
4980         buf[len] = NULLCHAR;
4981       } else {
4982         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4983       }
4984       SendToProgram(buf, cps);
4985     } else {
4986       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4987         AlphaRank(moveList[moveNum], 4);
4988         SendToProgram(moveList[moveNum], cps);
4989         AlphaRank(moveList[moveNum], 4); // and back
4990       } else
4991       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4992        * the engine. It would be nice to have a better way to identify castle
4993        * moves here. */
4994       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4995                                                                          && cps->useOOCastle) {
4996         int fromX = moveList[moveNum][0] - AAA;
4997         int fromY = moveList[moveNum][1] - ONE;
4998         int toX = moveList[moveNum][2] - AAA;
4999         int toY = moveList[moveNum][3] - ONE;
5000         if((boards[moveNum][fromY][fromX] == WhiteKing
5001             && boards[moveNum][toY][toX] == WhiteRook)
5002            || (boards[moveNum][fromY][fromX] == BlackKing
5003                && boards[moveNum][toY][toX] == BlackRook)) {
5004           if(toX > fromX) SendToProgram("O-O\n", cps);
5005           else SendToProgram("O-O-O\n", cps);
5006         }
5007         else SendToProgram(moveList[moveNum], cps);
5008       } else
5009       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5010         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5011           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5012           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5013                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5014         } else
5015           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5016                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5017         SendToProgram(buf, cps);
5018       }
5019       else SendToProgram(moveList[moveNum], cps);
5020       /* End of additions by Tord */
5021     }
5022
5023     /* [HGM] setting up the opening has brought engine in force mode! */
5024     /*       Send 'go' if we are in a mode where machine should play. */
5025     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5026         (gameMode == TwoMachinesPlay   ||
5027 #if ZIPPY
5028          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5029 #endif
5030          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5031         SendToProgram("go\n", cps);
5032   if (appData.debugMode) {
5033     fprintf(debugFP, "(extra)\n");
5034   }
5035     }
5036     setboardSpoiledMachineBlack = 0;
5037 }
5038
5039 void
5040 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5041 {
5042     char user_move[MSG_SIZ];
5043     char suffix[4];
5044
5045     if(gameInfo.variant == VariantSChess && promoChar) {
5046         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5047         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5048     } else suffix[0] = NULLCHAR;
5049
5050     switch (moveType) {
5051       default:
5052         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5053                 (int)moveType, fromX, fromY, toX, toY);
5054         DisplayError(user_move + strlen("say "), 0);
5055         break;
5056       case WhiteKingSideCastle:
5057       case BlackKingSideCastle:
5058       case WhiteQueenSideCastleWild:
5059       case BlackQueenSideCastleWild:
5060       /* PUSH Fabien */
5061       case WhiteHSideCastleFR:
5062       case BlackHSideCastleFR:
5063       /* POP Fabien */
5064         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5065         break;
5066       case WhiteQueenSideCastle:
5067       case BlackQueenSideCastle:
5068       case WhiteKingSideCastleWild:
5069       case BlackKingSideCastleWild:
5070       /* PUSH Fabien */
5071       case WhiteASideCastleFR:
5072       case BlackASideCastleFR:
5073       /* POP Fabien */
5074         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5075         break;
5076       case WhiteNonPromotion:
5077       case BlackNonPromotion:
5078         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5079         break;
5080       case WhitePromotion:
5081       case BlackPromotion:
5082         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5083           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5084                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5085                 PieceToChar(WhiteFerz));
5086         else if(gameInfo.variant == VariantGreat)
5087           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5089                 PieceToChar(WhiteMan));
5090         else
5091           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5092                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5093                 promoChar);
5094         break;
5095       case WhiteDrop:
5096       case BlackDrop:
5097       drop:
5098         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5099                  ToUpper(PieceToChar((ChessSquare) fromX)),
5100                  AAA + toX, ONE + toY);
5101         break;
5102       case IllegalMove:  /* could be a variant we don't quite understand */
5103         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5104       case NormalMove:
5105       case WhiteCapturesEnPassant:
5106       case BlackCapturesEnPassant:
5107         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5108                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5109         break;
5110     }
5111     SendToICS(user_move);
5112     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5113         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5114 }
5115
5116 void
5117 UploadGameEvent ()
5118 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5119     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5120     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5121     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5122       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5123       return;
5124     }
5125     if(gameMode != IcsExamining) { // is this ever not the case?
5126         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5127
5128         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5129           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5130         } else { // on FICS we must first go to general examine mode
5131           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5132         }
5133         if(gameInfo.variant != VariantNormal) {
5134             // try figure out wild number, as xboard names are not always valid on ICS
5135             for(i=1; i<=36; i++) {
5136               snprintf(buf, MSG_SIZ, "wild/%d", i);
5137                 if(StringToVariant(buf) == gameInfo.variant) break;
5138             }
5139             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5140             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5141             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5142         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5143         SendToICS(ics_prefix);
5144         SendToICS(buf);
5145         if(startedFromSetupPosition || backwardMostMove != 0) {
5146           fen = PositionToFEN(backwardMostMove, NULL);
5147           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5148             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5149             SendToICS(buf);
5150           } else { // FICS: everything has to set by separate bsetup commands
5151             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5152             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5153             SendToICS(buf);
5154             if(!WhiteOnMove(backwardMostMove)) {
5155                 SendToICS("bsetup tomove black\n");
5156             }
5157             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5158             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5159             SendToICS(buf);
5160             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5161             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5162             SendToICS(buf);
5163             i = boards[backwardMostMove][EP_STATUS];
5164             if(i >= 0) { // set e.p.
5165               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5166                 SendToICS(buf);
5167             }
5168             bsetup++;
5169           }
5170         }
5171       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5172             SendToICS("bsetup done\n"); // switch to normal examining.
5173     }
5174     for(i = backwardMostMove; i<last; i++) {
5175         char buf[20];
5176         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5177         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5178             int len = strlen(moveList[i]);
5179             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5180             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5181         }
5182         SendToICS(buf);
5183     }
5184     SendToICS(ics_prefix);
5185     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5186 }
5187
5188 void
5189 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5190 {
5191     if (rf == DROP_RANK) {
5192       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5193       sprintf(move, "%c@%c%c\n",
5194                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5195     } else {
5196         if (promoChar == 'x' || promoChar == NULLCHAR) {
5197           sprintf(move, "%c%c%c%c\n",
5198                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5199         } else {
5200             sprintf(move, "%c%c%c%c%c\n",
5201                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5202         }
5203     }
5204 }
5205
5206 void
5207 ProcessICSInitScript (FILE *f)
5208 {
5209     char buf[MSG_SIZ];
5210
5211     while (fgets(buf, MSG_SIZ, f)) {
5212         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5213     }
5214
5215     fclose(f);
5216 }
5217
5218
5219 static int lastX, lastY, selectFlag, dragging;
5220
5221 void
5222 Sweep (int step)
5223 {
5224     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5225     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5226     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5227     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5228     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5229     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5230     do {
5231         promoSweep -= step;
5232         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5233         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5234         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5235         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5236         if(!step) step = -1;
5237     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5238             appData.testLegality && (promoSweep == king ||
5239             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5240     if(toX >= 0) {
5241         int victim = boards[currentMove][toY][toX];
5242         boards[currentMove][toY][toX] = promoSweep;
5243         DrawPosition(FALSE, boards[currentMove]);
5244         boards[currentMove][toY][toX] = victim;
5245     } else
5246     ChangeDragPiece(promoSweep);
5247 }
5248
5249 int
5250 PromoScroll (int x, int y)
5251 {
5252   int step = 0;
5253
5254   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5255   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5256   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5257   if(!step) return FALSE;
5258   lastX = x; lastY = y;
5259   if((promoSweep < BlackPawn) == flipView) step = -step;
5260   if(step > 0) selectFlag = 1;
5261   if(!selectFlag) Sweep(step);
5262   return FALSE;
5263 }
5264
5265 void
5266 NextPiece (int step)
5267 {
5268     ChessSquare piece = boards[currentMove][toY][toX];
5269     do {
5270         pieceSweep -= step;
5271         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5272         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5273         if(!step) step = -1;
5274     } while(PieceToChar(pieceSweep) == '.');
5275     boards[currentMove][toY][toX] = pieceSweep;
5276     DrawPosition(FALSE, boards[currentMove]);
5277     boards[currentMove][toY][toX] = piece;
5278 }
5279 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5280 void
5281 AlphaRank (char *move, int n)
5282 {
5283 //    char *p = move, c; int x, y;
5284
5285     if (appData.debugMode) {
5286         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5287     }
5288
5289     if(move[1]=='*' &&
5290        move[2]>='0' && move[2]<='9' &&
5291        move[3]>='a' && move[3]<='x'    ) {
5292         move[1] = '@';
5293         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5294         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5295     } else
5296     if(move[0]>='0' && move[0]<='9' &&
5297        move[1]>='a' && move[1]<='x' &&
5298        move[2]>='0' && move[2]<='9' &&
5299        move[3]>='a' && move[3]<='x'    ) {
5300         /* input move, Shogi -> normal */
5301         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5302         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5303         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5304         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5305     } else
5306     if(move[1]=='@' &&
5307        move[3]>='0' && move[3]<='9' &&
5308        move[2]>='a' && move[2]<='x'    ) {
5309         move[1] = '*';
5310         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5311         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5312     } else
5313     if(
5314        move[0]>='a' && move[0]<='x' &&
5315        move[3]>='0' && move[3]<='9' &&
5316        move[2]>='a' && move[2]<='x'    ) {
5317          /* output move, normal -> Shogi */
5318         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5319         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5320         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5321         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5322         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5323     }
5324     if (appData.debugMode) {
5325         fprintf(debugFP, "   out = '%s'\n", move);
5326     }
5327 }
5328
5329 char yy_textstr[8000];
5330
5331 /* Parser for moves from gnuchess, ICS, or user typein box */
5332 Boolean
5333 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5334 {
5335     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5336
5337     switch (*moveType) {
5338       case WhitePromotion:
5339       case BlackPromotion:
5340       case WhiteNonPromotion:
5341       case BlackNonPromotion:
5342       case NormalMove:
5343       case WhiteCapturesEnPassant:
5344       case BlackCapturesEnPassant:
5345       case WhiteKingSideCastle:
5346       case WhiteQueenSideCastle:
5347       case BlackKingSideCastle:
5348       case BlackQueenSideCastle:
5349       case WhiteKingSideCastleWild:
5350       case WhiteQueenSideCastleWild:
5351       case BlackKingSideCastleWild:
5352       case BlackQueenSideCastleWild:
5353       /* Code added by Tord: */
5354       case WhiteHSideCastleFR:
5355       case WhiteASideCastleFR:
5356       case BlackHSideCastleFR:
5357       case BlackASideCastleFR:
5358       /* End of code added by Tord */
5359       case IllegalMove:         /* bug or odd chess variant */
5360         *fromX = currentMoveString[0] - AAA;
5361         *fromY = currentMoveString[1] - ONE;
5362         *toX = currentMoveString[2] - AAA;
5363         *toY = currentMoveString[3] - ONE;
5364         *promoChar = currentMoveString[4];
5365         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5366             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5367     if (appData.debugMode) {
5368         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5369     }
5370             *fromX = *fromY = *toX = *toY = 0;
5371             return FALSE;
5372         }
5373         if (appData.testLegality) {
5374           return (*moveType != IllegalMove);
5375         } else {
5376           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5377                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5378         }
5379
5380       case WhiteDrop:
5381       case BlackDrop:
5382         *fromX = *moveType == WhiteDrop ?
5383           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5384           (int) CharToPiece(ToLower(currentMoveString[0]));
5385         *fromY = DROP_RANK;
5386         *toX = currentMoveString[2] - AAA;
5387         *toY = currentMoveString[3] - ONE;
5388         *promoChar = NULLCHAR;
5389         return TRUE;
5390
5391       case AmbiguousMove:
5392       case ImpossibleMove:
5393       case EndOfFile:
5394       case ElapsedTime:
5395       case Comment:
5396       case PGNTag:
5397       case NAG:
5398       case WhiteWins:
5399       case BlackWins:
5400       case GameIsDrawn:
5401       default:
5402     if (appData.debugMode) {
5403         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5404     }
5405         /* bug? */
5406         *fromX = *fromY = *toX = *toY = 0;
5407         *promoChar = NULLCHAR;
5408         return FALSE;
5409     }
5410 }
5411
5412 Boolean pushed = FALSE;
5413 char *lastParseAttempt;
5414
5415 void
5416 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5417 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5418   int fromX, fromY, toX, toY; char promoChar;
5419   ChessMove moveType;
5420   Boolean valid;
5421   int nr = 0;
5422
5423   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5424   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5425     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5426     pushed = TRUE;
5427   }
5428   endPV = forwardMostMove;
5429   do {
5430     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5431     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5432     lastParseAttempt = pv;
5433     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5434     if(!valid && nr == 0 &&
5435        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5436         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5437         // Hande case where played move is different from leading PV move
5438         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5439         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5440         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5441         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5442           endPV += 2; // if position different, keep this
5443           moveList[endPV-1][0] = fromX + AAA;
5444           moveList[endPV-1][1] = fromY + ONE;
5445           moveList[endPV-1][2] = toX + AAA;
5446           moveList[endPV-1][3] = toY + ONE;
5447           parseList[endPV-1][0] = NULLCHAR;
5448           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5449         }
5450       }
5451     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5452     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5453     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5454     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5455         valid++; // allow comments in PV
5456         continue;
5457     }
5458     nr++;
5459     if(endPV+1 > framePtr) break; // no space, truncate
5460     if(!valid) break;
5461     endPV++;
5462     CopyBoard(boards[endPV], boards[endPV-1]);
5463     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5464     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5465     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5466     CoordsToAlgebraic(boards[endPV - 1],
5467                              PosFlags(endPV - 1),
5468                              fromY, fromX, toY, toX, promoChar,
5469                              parseList[endPV - 1]);
5470   } while(valid);
5471   if(atEnd == 2) return; // used hidden, for PV conversion
5472   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5473   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5474   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5475                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5476   DrawPosition(TRUE, boards[currentMove]);
5477 }
5478
5479 int
5480 MultiPV (ChessProgramState *cps)
5481 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5482         int i;
5483         for(i=0; i<cps->nrOptions; i++)
5484             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5485                 return i;
5486         return -1;
5487 }
5488
5489 Boolean
5490 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5491 {
5492         int startPV, multi, lineStart, origIndex = index;
5493         char *p, buf2[MSG_SIZ];
5494         ChessProgramState *cps = (pane ? &second : &first);
5495
5496         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5497         lastX = x; lastY = y;
5498         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5499         lineStart = startPV = index;
5500         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5501         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5502         index = startPV;
5503         do{ while(buf[index] && buf[index] != '\n') index++;
5504         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5505         buf[index] = 0;
5506         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5507                 int n = cps->option[multi].value;
5508                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5509                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5510                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5511                 cps->option[multi].value = n;
5512                 *start = *end = 0;
5513                 return FALSE;
5514         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5515                 ExcludeClick(origIndex - lineStart);
5516                 return FALSE;
5517         }
5518         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5519         *start = startPV; *end = index-1;
5520         return TRUE;
5521 }
5522
5523 char *
5524 PvToSAN (char *pv)
5525 {
5526         static char buf[10*MSG_SIZ];
5527         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5528         *buf = NULLCHAR;
5529         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5530         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5531         for(i = forwardMostMove; i<endPV; i++){
5532             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5533             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5534             k += strlen(buf+k);
5535         }
5536         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5537         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5538         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5539         endPV = savedEnd;
5540         return buf;
5541 }
5542
5543 Boolean
5544 LoadPV (int x, int y)
5545 { // called on right mouse click to load PV
5546   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5547   lastX = x; lastY = y;
5548   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5549   return TRUE;
5550 }
5551
5552 void
5553 UnLoadPV ()
5554 {
5555   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5556   if(endPV < 0) return;
5557   if(appData.autoCopyPV) CopyFENToClipboard();
5558   endPV = -1;
5559   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5560         Boolean saveAnimate = appData.animate;
5561         if(pushed) {
5562             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5563                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5564             } else storedGames--; // abandon shelved tail of original game
5565         }
5566         pushed = FALSE;
5567         forwardMostMove = currentMove;
5568         currentMove = oldFMM;
5569         appData.animate = FALSE;
5570         ToNrEvent(forwardMostMove);
5571         appData.animate = saveAnimate;
5572   }
5573   currentMove = forwardMostMove;
5574   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5575   ClearPremoveHighlights();
5576   DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 MovePV (int x, int y, int h)
5581 { // step through PV based on mouse coordinates (called on mouse move)
5582   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5583
5584   // we must somehow check if right button is still down (might be released off board!)
5585   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5586   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5587   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5588   if(!step) return;
5589   lastX = x; lastY = y;
5590
5591   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5592   if(endPV < 0) return;
5593   if(y < margin) step = 1; else
5594   if(y > h - margin) step = -1;
5595   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5596   currentMove += step;
5597   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5598   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5599                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5600   DrawPosition(FALSE, boards[currentMove]);
5601 }
5602
5603
5604 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5605 // All positions will have equal probability, but the current method will not provide a unique
5606 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5607 #define DARK 1
5608 #define LITE 2
5609 #define ANY 3
5610
5611 int squaresLeft[4];
5612 int piecesLeft[(int)BlackPawn];
5613 int seed, nrOfShuffles;
5614
5615 void
5616 GetPositionNumber ()
5617 {       // sets global variable seed
5618         int i;
5619
5620         seed = appData.defaultFrcPosition;
5621         if(seed < 0) { // randomize based on time for negative FRC position numbers
5622                 for(i=0; i<50; i++) seed += random();
5623                 seed = random() ^ random() >> 8 ^ random() << 8;
5624                 if(seed<0) seed = -seed;
5625         }
5626 }
5627
5628 int
5629 put (Board board, int pieceType, int rank, int n, int shade)
5630 // put the piece on the (n-1)-th empty squares of the given shade
5631 {
5632         int i;
5633
5634         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5635                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5636                         board[rank][i] = (ChessSquare) pieceType;
5637                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5638                         squaresLeft[ANY]--;
5639                         piecesLeft[pieceType]--;
5640                         return i;
5641                 }
5642         }
5643         return -1;
5644 }
5645
5646
5647 void
5648 AddOnePiece (Board board, int pieceType, int rank, int shade)
5649 // calculate where the next piece goes, (any empty square), and put it there
5650 {
5651         int i;
5652
5653         i = seed % squaresLeft[shade];
5654         nrOfShuffles *= squaresLeft[shade];
5655         seed /= squaresLeft[shade];
5656         put(board, pieceType, rank, i, shade);
5657 }
5658
5659 void
5660 AddTwoPieces (Board board, int pieceType, int rank)
5661 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5662 {
5663         int i, n=squaresLeft[ANY], j=n-1, k;
5664
5665         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5666         i = seed % k;  // pick one
5667         nrOfShuffles *= k;
5668         seed /= k;
5669         while(i >= j) i -= j--;
5670         j = n - 1 - j; i += j;
5671         put(board, pieceType, rank, j, ANY);
5672         put(board, pieceType, rank, i, ANY);
5673 }
5674
5675 void
5676 SetUpShuffle (Board board, int number)
5677 {
5678         int i, p, first=1;
5679
5680         GetPositionNumber(); nrOfShuffles = 1;
5681
5682         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5683         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5684         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5685
5686         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5687
5688         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5689             p = (int) board[0][i];
5690             if(p < (int) BlackPawn) piecesLeft[p] ++;
5691             board[0][i] = EmptySquare;
5692         }
5693
5694         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5695             // shuffles restricted to allow normal castling put KRR first
5696             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5697                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5698             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5699                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5700             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5701                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5702             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5703                 put(board, WhiteRook, 0, 0, ANY);
5704             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5705         }
5706
5707         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5708             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5709             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5710                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5711                 while(piecesLeft[p] >= 2) {
5712                     AddOnePiece(board, p, 0, LITE);
5713                     AddOnePiece(board, p, 0, DARK);
5714                 }
5715                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5716             }
5717
5718         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5719             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5720             // but we leave King and Rooks for last, to possibly obey FRC restriction
5721             if(p == (int)WhiteRook) continue;
5722             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5723             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5724         }
5725
5726         // now everything is placed, except perhaps King (Unicorn) and Rooks
5727
5728         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5729             // Last King gets castling rights
5730             while(piecesLeft[(int)WhiteUnicorn]) {
5731                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5732                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5733             }
5734
5735             while(piecesLeft[(int)WhiteKing]) {
5736                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5737                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5738             }
5739
5740
5741         } else {
5742             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5743             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5744         }
5745
5746         // Only Rooks can be left; simply place them all
5747         while(piecesLeft[(int)WhiteRook]) {
5748                 i = put(board, WhiteRook, 0, 0, ANY);
5749                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5750                         if(first) {
5751                                 first=0;
5752                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5753                         }
5754                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5755                 }
5756         }
5757         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5758             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5759         }
5760
5761         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5762 }
5763
5764 int
5765 SetCharTable (char *table, const char * map)
5766 /* [HGM] moved here from winboard.c because of its general usefulness */
5767 /*       Basically a safe strcpy that uses the last character as King */
5768 {
5769     int result = FALSE; int NrPieces;
5770
5771     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5772                     && NrPieces >= 12 && !(NrPieces&1)) {
5773         int i; /* [HGM] Accept even length from 12 to 34 */
5774
5775         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5776         for( i=0; i<NrPieces/2-1; i++ ) {
5777             table[i] = map[i];
5778             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5779         }
5780         table[(int) WhiteKing]  = map[NrPieces/2-1];
5781         table[(int) BlackKing]  = map[NrPieces-1];
5782
5783         result = TRUE;
5784     }
5785
5786     return result;
5787 }
5788
5789 void
5790 Prelude (Board board)
5791 {       // [HGM] superchess: random selection of exo-pieces
5792         int i, j, k; ChessSquare p;
5793         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5794
5795         GetPositionNumber(); // use FRC position number
5796
5797         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5798             SetCharTable(pieceToChar, appData.pieceToCharTable);
5799             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5800                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5801         }
5802
5803         j = seed%4;                 seed /= 4;
5804         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5805         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5806         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5807         j = seed%3 + (seed%3 >= j); seed /= 3;
5808         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5809         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5810         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5811         j = seed%3;                 seed /= 3;
5812         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5813         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5814         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5815         j = seed%2 + (seed%2 >= j); seed /= 2;
5816         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5817         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5818         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5819         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5820         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5821         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5822         put(board, exoPieces[0],    0, 0, ANY);
5823         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5824 }
5825
5826 void
5827 InitPosition (int redraw)
5828 {
5829     ChessSquare (* pieces)[BOARD_FILES];
5830     int i, j, pawnRow, overrule,
5831     oldx = gameInfo.boardWidth,
5832     oldy = gameInfo.boardHeight,
5833     oldh = gameInfo.holdingsWidth;
5834     static int oldv;
5835
5836     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5837
5838     /* [AS] Initialize pv info list [HGM] and game status */
5839     {
5840         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5841             pvInfoList[i].depth = 0;
5842             boards[i][EP_STATUS] = EP_NONE;
5843             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5844         }
5845
5846         initialRulePlies = 0; /* 50-move counter start */
5847
5848         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5849         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5850     }
5851
5852
5853     /* [HGM] logic here is completely changed. In stead of full positions */
5854     /* the initialized data only consist of the two backranks. The switch */
5855     /* selects which one we will use, which is than copied to the Board   */
5856     /* initialPosition, which for the rest is initialized by Pawns and    */
5857     /* empty squares. This initial position is then copied to boards[0],  */
5858     /* possibly after shuffling, so that it remains available.            */
5859
5860     gameInfo.holdingsWidth = 0; /* default board sizes */
5861     gameInfo.boardWidth    = 8;
5862     gameInfo.boardHeight   = 8;
5863     gameInfo.holdingsSize  = 0;
5864     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5865     for(i=0; i<BOARD_FILES-2; i++)
5866       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5867     initialPosition[EP_STATUS] = EP_NONE;
5868     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5869     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5870          SetCharTable(pieceNickName, appData.pieceNickNames);
5871     else SetCharTable(pieceNickName, "............");
5872     pieces = FIDEArray;
5873
5874     switch (gameInfo.variant) {
5875     case VariantFischeRandom:
5876       shuffleOpenings = TRUE;
5877     default:
5878       break;
5879     case VariantShatranj:
5880       pieces = ShatranjArray;
5881       nrCastlingRights = 0;
5882       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5883       break;
5884     case VariantMakruk:
5885       pieces = makrukArray;
5886       nrCastlingRights = 0;
5887       startedFromSetupPosition = TRUE;
5888       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5889       break;
5890     case VariantTwoKings:
5891       pieces = twoKingsArray;
5892       break;
5893     case VariantGrand:
5894       pieces = GrandArray;
5895       nrCastlingRights = 0;
5896       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5897       gameInfo.boardWidth = 10;
5898       gameInfo.boardHeight = 10;
5899       gameInfo.holdingsSize = 7;
5900       break;
5901     case VariantCapaRandom:
5902       shuffleOpenings = TRUE;
5903     case VariantCapablanca:
5904       pieces = CapablancaArray;
5905       gameInfo.boardWidth = 10;
5906       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5907       break;
5908     case VariantGothic:
5909       pieces = GothicArray;
5910       gameInfo.boardWidth = 10;
5911       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5912       break;
5913     case VariantSChess:
5914       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5915       gameInfo.holdingsSize = 7;
5916       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5917       break;
5918     case VariantJanus:
5919       pieces = JanusArray;
5920       gameInfo.boardWidth = 10;
5921       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5922       nrCastlingRights = 6;
5923         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5924         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5925         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5926         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5927         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5928         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5929       break;
5930     case VariantFalcon:
5931       pieces = FalconArray;
5932       gameInfo.boardWidth = 10;
5933       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5934       break;
5935     case VariantXiangqi:
5936       pieces = XiangqiArray;
5937       gameInfo.boardWidth  = 9;
5938       gameInfo.boardHeight = 10;
5939       nrCastlingRights = 0;
5940       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5941       break;
5942     case VariantShogi:
5943       pieces = ShogiArray;
5944       gameInfo.boardWidth  = 9;
5945       gameInfo.boardHeight = 9;
5946       gameInfo.holdingsSize = 7;
5947       nrCastlingRights = 0;
5948       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5949       break;
5950     case VariantCourier:
5951       pieces = CourierArray;
5952       gameInfo.boardWidth  = 12;
5953       nrCastlingRights = 0;
5954       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5955       break;
5956     case VariantKnightmate:
5957       pieces = KnightmateArray;
5958       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5959       break;
5960     case VariantSpartan:
5961       pieces = SpartanArray;
5962       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5963       break;
5964     case VariantFairy:
5965       pieces = fairyArray;
5966       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5967       break;
5968     case VariantGreat:
5969       pieces = GreatArray;
5970       gameInfo.boardWidth = 10;
5971       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5972       gameInfo.holdingsSize = 8;
5973       break;
5974     case VariantSuper:
5975       pieces = FIDEArray;
5976       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5977       gameInfo.holdingsSize = 8;
5978       startedFromSetupPosition = TRUE;
5979       break;
5980     case VariantCrazyhouse:
5981     case VariantBughouse:
5982       pieces = FIDEArray;
5983       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5984       gameInfo.holdingsSize = 5;
5985       break;
5986     case VariantWildCastle:
5987       pieces = FIDEArray;
5988       /* !!?shuffle with kings guaranteed to be on d or e file */
5989       shuffleOpenings = 1;
5990       break;
5991     case VariantNoCastle:
5992       pieces = FIDEArray;
5993       nrCastlingRights = 0;
5994       /* !!?unconstrained back-rank shuffle */
5995       shuffleOpenings = 1;
5996       break;
5997     }
5998
5999     overrule = 0;
6000     if(appData.NrFiles >= 0) {
6001         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6002         gameInfo.boardWidth = appData.NrFiles;
6003     }
6004     if(appData.NrRanks >= 0) {
6005         gameInfo.boardHeight = appData.NrRanks;
6006     }
6007     if(appData.holdingsSize >= 0) {
6008         i = appData.holdingsSize;
6009         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6010         gameInfo.holdingsSize = i;
6011     }
6012     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6013     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6014         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6015
6016     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6017     if(pawnRow < 1) pawnRow = 1;
6018     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6019
6020     /* User pieceToChar list overrules defaults */
6021     if(appData.pieceToCharTable != NULL)
6022         SetCharTable(pieceToChar, appData.pieceToCharTable);
6023
6024     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6025
6026         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6027             s = (ChessSquare) 0; /* account holding counts in guard band */
6028         for( i=0; i<BOARD_HEIGHT; i++ )
6029             initialPosition[i][j] = s;
6030
6031         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6032         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6033         initialPosition[pawnRow][j] = WhitePawn;
6034         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6035         if(gameInfo.variant == VariantXiangqi) {
6036             if(j&1) {
6037                 initialPosition[pawnRow][j] =
6038                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6039                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6040                    initialPosition[2][j] = WhiteCannon;
6041                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6042                 }
6043             }
6044         }
6045         if(gameInfo.variant == VariantGrand) {
6046             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6047                initialPosition[0][j] = WhiteRook;
6048                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6049             }
6050         }
6051         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6052     }
6053     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6054
6055             j=BOARD_LEFT+1;
6056             initialPosition[1][j] = WhiteBishop;
6057             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6058             j=BOARD_RGHT-2;
6059             initialPosition[1][j] = WhiteRook;
6060             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6061     }
6062
6063     if( nrCastlingRights == -1) {
6064         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6065         /*       This sets default castling rights from none to normal corners   */
6066         /* Variants with other castling rights must set them themselves above    */
6067         nrCastlingRights = 6;
6068
6069         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6070         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6071         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6072         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6073         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6074         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6075      }
6076
6077      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6078      if(gameInfo.variant == VariantGreat) { // promotion commoners
6079         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6080         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6081         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6082         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6083      }
6084      if( gameInfo.variant == VariantSChess ) {
6085       initialPosition[1][0] = BlackMarshall;
6086       initialPosition[2][0] = BlackAngel;
6087       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6088       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6089       initialPosition[1][1] = initialPosition[2][1] =
6090       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6091      }
6092   if (appData.debugMode) {
6093     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6094   }
6095     if(shuffleOpenings) {
6096         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6097         startedFromSetupPosition = TRUE;
6098     }
6099     if(startedFromPositionFile) {
6100       /* [HGM] loadPos: use PositionFile for every new game */
6101       CopyBoard(initialPosition, filePosition);
6102       for(i=0; i<nrCastlingRights; i++)
6103           initialRights[i] = filePosition[CASTLING][i];
6104       startedFromSetupPosition = TRUE;
6105     }
6106
6107     CopyBoard(boards[0], initialPosition);
6108
6109     if(oldx != gameInfo.boardWidth ||
6110        oldy != gameInfo.boardHeight ||
6111        oldv != gameInfo.variant ||
6112        oldh != gameInfo.holdingsWidth
6113                                          )
6114             InitDrawingSizes(-2 ,0);
6115
6116     oldv = gameInfo.variant;
6117     if (redraw)
6118       DrawPosition(TRUE, boards[currentMove]);
6119 }
6120
6121 void
6122 SendBoard (ChessProgramState *cps, int moveNum)
6123 {
6124     char message[MSG_SIZ];
6125
6126     if (cps->useSetboard) {
6127       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6128       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6129       SendToProgram(message, cps);
6130       free(fen);
6131
6132     } else {
6133       ChessSquare *bp;
6134       int i, j, left=0, right=BOARD_WIDTH;
6135       /* Kludge to set black to move, avoiding the troublesome and now
6136        * deprecated "black" command.
6137        */
6138       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6139         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6140
6141       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6142
6143       SendToProgram("edit\n", cps);
6144       SendToProgram("#\n", cps);
6145       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6146         bp = &boards[moveNum][i][left];
6147         for (j = left; j < right; j++, bp++) {
6148           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6149           if ((int) *bp < (int) BlackPawn) {
6150             if(j == BOARD_RGHT+1)
6151                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6152             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6153             if(message[0] == '+' || message[0] == '~') {
6154               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6155                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6156                         AAA + j, ONE + i);
6157             }
6158             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6159                 message[1] = BOARD_RGHT   - 1 - j + '1';
6160                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6161             }
6162             SendToProgram(message, cps);
6163           }
6164         }
6165       }
6166
6167       SendToProgram("c\n", cps);
6168       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6169         bp = &boards[moveNum][i][left];
6170         for (j = left; j < right; j++, bp++) {
6171           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6172           if (((int) *bp != (int) EmptySquare)
6173               && ((int) *bp >= (int) BlackPawn)) {
6174             if(j == BOARD_LEFT-2)
6175                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6176             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6177                     AAA + j, ONE + i);
6178             if(message[0] == '+' || message[0] == '~') {
6179               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6180                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6181                         AAA + j, ONE + i);
6182             }
6183             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6184                 message[1] = BOARD_RGHT   - 1 - j + '1';
6185                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6186             }
6187             SendToProgram(message, cps);
6188           }
6189         }
6190       }
6191
6192       SendToProgram(".\n", cps);
6193     }
6194     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6195 }
6196
6197 char exclusionHeader[MSG_SIZ];
6198 int exCnt, excludePtr;
6199 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6200 static Exclusion excluTab[200];
6201 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6202
6203 static void
6204 WriteMap (int s)
6205 {
6206     int j;
6207     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6208     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6209 }
6210
6211 static void
6212 ClearMap ()
6213 {
6214     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6215     excludePtr = 24; exCnt = 0;
6216     WriteMap(0);
6217 }
6218
6219 static void
6220 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6221 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6222     char buf[2*MOVE_LEN], *p;
6223     Exclusion *e = excluTab;
6224     int i;
6225     for(i=0; i<exCnt; i++)
6226         if(e[i].ff == fromX && e[i].fr == fromY &&
6227            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6228     if(i == exCnt) { // was not in exclude list; add it
6229         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6230         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6231             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6232             return; // abort
6233         }
6234         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6235         excludePtr++; e[i].mark = excludePtr++;
6236         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6237         exCnt++;
6238     }
6239     exclusionHeader[e[i].mark] = state;
6240 }
6241
6242 static int
6243 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6244 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6245     char buf[MSG_SIZ];
6246     int j, k;
6247     ChessMove moveType;
6248     if((signed char)promoChar == -1) { // kludge to indicate best move
6249         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6250             return 1; // if unparsable, abort
6251     }
6252     // update exclusion map (resolving toggle by consulting existing state)
6253     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6254     j = k%8; k >>= 3;
6255     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6256     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6257          excludeMap[k] |=   1<<j;
6258     else excludeMap[k] &= ~(1<<j);
6259     // update header
6260     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6261     // inform engine
6262     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6263     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6264     SendToBoth(buf);
6265     return (state == '+');
6266 }
6267
6268 static void
6269 ExcludeClick (int index)
6270 {
6271     int i, j;
6272     Exclusion *e = excluTab;
6273     if(index < 25) { // none, best or tail clicked
6274         if(index < 13) { // none: include all
6275             WriteMap(0); // clear map
6276             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6277             SendToBoth("include all\n"); // and inform engine
6278         } else if(index > 18) { // tail
6279             if(exclusionHeader[19] == '-') { // tail was excluded
6280                 SendToBoth("include all\n");
6281                 WriteMap(0); // clear map completely
6282                 // now re-exclude selected moves
6283                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6284                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6285             } else { // tail was included or in mixed state
6286                 SendToBoth("exclude all\n");
6287                 WriteMap(0xFF); // fill map completely
6288                 // now re-include selected moves
6289                 j = 0; // count them
6290                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6291                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6292                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6293             }
6294         } else { // best
6295             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6296         }
6297     } else {
6298         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6299             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6300             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6301             break;
6302         }
6303     }
6304 }
6305
6306 ChessSquare
6307 DefaultPromoChoice (int white)
6308 {
6309     ChessSquare result;
6310     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6311         result = WhiteFerz; // no choice
6312     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6313         result= WhiteKing; // in Suicide Q is the last thing we want
6314     else if(gameInfo.variant == VariantSpartan)
6315         result = white ? WhiteQueen : WhiteAngel;
6316     else result = WhiteQueen;
6317     if(!white) result = WHITE_TO_BLACK result;
6318     return result;
6319 }
6320
6321 static int autoQueen; // [HGM] oneclick
6322
6323 int
6324 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6325 {
6326     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6327     /* [HGM] add Shogi promotions */
6328     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6329     ChessSquare piece;
6330     ChessMove moveType;
6331     Boolean premove;
6332
6333     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6334     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6335
6336     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6337       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6338         return FALSE;
6339
6340     piece = boards[currentMove][fromY][fromX];
6341     if(gameInfo.variant == VariantShogi) {
6342         promotionZoneSize = BOARD_HEIGHT/3;
6343         highestPromotingPiece = (int)WhiteFerz;
6344     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6345         promotionZoneSize = 3;
6346     }
6347
6348     // Treat Lance as Pawn when it is not representing Amazon
6349     if(gameInfo.variant != VariantSuper) {
6350         if(piece == WhiteLance) piece = WhitePawn; else
6351         if(piece == BlackLance) piece = BlackPawn;
6352     }
6353
6354     // next weed out all moves that do not touch the promotion zone at all
6355     if((int)piece >= BlackPawn) {
6356         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6357              return FALSE;
6358         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6359     } else {
6360         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6361            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6362     }
6363
6364     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6365
6366     // weed out mandatory Shogi promotions
6367     if(gameInfo.variant == VariantShogi) {
6368         if(piece >= BlackPawn) {
6369             if(toY == 0 && piece == BlackPawn ||
6370                toY == 0 && piece == BlackQueen ||
6371                toY <= 1 && piece == BlackKnight) {
6372                 *promoChoice = '+';
6373                 return FALSE;
6374             }
6375         } else {
6376             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6377                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6378                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6379                 *promoChoice = '+';
6380                 return FALSE;
6381             }
6382         }
6383     }
6384
6385     // weed out obviously illegal Pawn moves
6386     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6387         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6388         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6389         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6390         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6391         // note we are not allowed to test for valid (non-)capture, due to premove
6392     }
6393
6394     // we either have a choice what to promote to, or (in Shogi) whether to promote
6395     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6396         *promoChoice = PieceToChar(BlackFerz);  // no choice
6397         return FALSE;
6398     }
6399     // no sense asking what we must promote to if it is going to explode...
6400     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6401         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6402         return FALSE;
6403     }
6404     // give caller the default choice even if we will not make it
6405     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6406     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6407     if(        sweepSelect && gameInfo.variant != VariantGreat
6408                            && gameInfo.variant != VariantGrand
6409                            && gameInfo.variant != VariantSuper) return FALSE;
6410     if(autoQueen) return FALSE; // predetermined
6411
6412     // suppress promotion popup on illegal moves that are not premoves
6413     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6414               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6415     if(appData.testLegality && !premove) {
6416         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6417                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6418         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6419             return FALSE;
6420     }
6421
6422     return TRUE;
6423 }
6424
6425 int
6426 InPalace (int row, int column)
6427 {   /* [HGM] for Xiangqi */
6428     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6429          column < (BOARD_WIDTH + 4)/2 &&
6430          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6431     return FALSE;
6432 }
6433
6434 int
6435 PieceForSquare (int x, int y)
6436 {
6437   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6438      return -1;
6439   else
6440      return boards[currentMove][y][x];
6441 }
6442
6443 int
6444 OKToStartUserMove (int x, int y)
6445 {
6446     ChessSquare from_piece;
6447     int white_piece;
6448
6449     if (matchMode) return FALSE;
6450     if (gameMode == EditPosition) return TRUE;
6451
6452     if (x >= 0 && y >= 0)
6453       from_piece = boards[currentMove][y][x];
6454     else
6455       from_piece = EmptySquare;
6456
6457     if (from_piece == EmptySquare) return FALSE;
6458
6459     white_piece = (int)from_piece >= (int)WhitePawn &&
6460       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6461
6462     switch (gameMode) {
6463       case AnalyzeFile:
6464       case TwoMachinesPlay:
6465       case EndOfGame:
6466         return FALSE;
6467
6468       case IcsObserving:
6469       case IcsIdle:
6470         return FALSE;
6471
6472       case MachinePlaysWhite:
6473       case IcsPlayingBlack:
6474         if (appData.zippyPlay) return FALSE;
6475         if (white_piece) {
6476             DisplayMoveError(_("You are playing Black"));
6477             return FALSE;
6478         }
6479         break;
6480
6481       case MachinePlaysBlack:
6482       case IcsPlayingWhite:
6483         if (appData.zippyPlay) return FALSE;
6484         if (!white_piece) {
6485             DisplayMoveError(_("You are playing White"));
6486             return FALSE;
6487         }
6488         break;
6489
6490       case PlayFromGameFile:
6491             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6492       case EditGame:
6493         if (!white_piece && WhiteOnMove(currentMove)) {
6494             DisplayMoveError(_("It is White's turn"));
6495             return FALSE;
6496         }
6497         if (white_piece && !WhiteOnMove(currentMove)) {
6498             DisplayMoveError(_("It is Black's turn"));
6499             return FALSE;
6500         }
6501         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6502             /* Editing correspondence game history */
6503             /* Could disallow this or prompt for confirmation */
6504             cmailOldMove = -1;
6505         }
6506         break;
6507
6508       case BeginningOfGame:
6509         if (appData.icsActive) return FALSE;
6510         if (!appData.noChessProgram) {
6511             if (!white_piece) {
6512                 DisplayMoveError(_("You are playing White"));
6513                 return FALSE;
6514             }
6515         }
6516         break;
6517
6518       case Training:
6519         if (!white_piece && WhiteOnMove(currentMove)) {
6520             DisplayMoveError(_("It is White's turn"));
6521             return FALSE;
6522         }
6523         if (white_piece && !WhiteOnMove(currentMove)) {
6524             DisplayMoveError(_("It is Black's turn"));
6525             return FALSE;
6526         }
6527         break;
6528
6529       default:
6530       case IcsExamining:
6531         break;
6532     }
6533     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6534         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6535         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6536         && gameMode != AnalyzeFile && gameMode != Training) {
6537         DisplayMoveError(_("Displayed position is not current"));
6538         return FALSE;
6539     }
6540     return TRUE;
6541 }
6542
6543 Boolean
6544 OnlyMove (int *x, int *y, Boolean captures)
6545 {
6546     DisambiguateClosure cl;
6547     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6548     switch(gameMode) {
6549       case MachinePlaysBlack:
6550       case IcsPlayingWhite:
6551       case BeginningOfGame:
6552         if(!WhiteOnMove(currentMove)) return FALSE;
6553         break;
6554       case MachinePlaysWhite:
6555       case IcsPlayingBlack:
6556         if(WhiteOnMove(currentMove)) return FALSE;
6557         break;
6558       case EditGame:
6559         break;
6560       default:
6561         return FALSE;
6562     }
6563     cl.pieceIn = EmptySquare;
6564     cl.rfIn = *y;
6565     cl.ffIn = *x;
6566     cl.rtIn = -1;
6567     cl.ftIn = -1;
6568     cl.promoCharIn = NULLCHAR;
6569     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6570     if( cl.kind == NormalMove ||
6571         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6572         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6573         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6574       fromX = cl.ff;
6575       fromY = cl.rf;
6576       *x = cl.ft;
6577       *y = cl.rt;
6578       return TRUE;
6579     }
6580     if(cl.kind != ImpossibleMove) return FALSE;
6581     cl.pieceIn = EmptySquare;
6582     cl.rfIn = -1;
6583     cl.ffIn = -1;
6584     cl.rtIn = *y;
6585     cl.ftIn = *x;
6586     cl.promoCharIn = NULLCHAR;
6587     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6588     if( cl.kind == NormalMove ||
6589         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6590         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6591         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6592       fromX = cl.ff;
6593       fromY = cl.rf;
6594       *x = cl.ft;
6595       *y = cl.rt;
6596       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6597       return TRUE;
6598     }
6599     return FALSE;
6600 }
6601
6602 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6603 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6604 int lastLoadGameUseList = FALSE;
6605 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6606 ChessMove lastLoadGameStart = EndOfFile;
6607 int doubleClick;
6608
6609 void
6610 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6611 {
6612     ChessMove moveType;
6613     ChessSquare pup;
6614     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6615
6616     /* Check if the user is playing in turn.  This is complicated because we
6617        let the user "pick up" a piece before it is his turn.  So the piece he
6618        tried to pick up may have been captured by the time he puts it down!
6619        Therefore we use the color the user is supposed to be playing in this
6620        test, not the color of the piece that is currently on the starting
6621        square---except in EditGame mode, where the user is playing both
6622        sides; fortunately there the capture race can't happen.  (It can
6623        now happen in IcsExamining mode, but that's just too bad.  The user
6624        will get a somewhat confusing message in that case.)
6625        */
6626
6627     switch (gameMode) {
6628       case AnalyzeFile:
6629       case TwoMachinesPlay:
6630       case EndOfGame:
6631       case IcsObserving:
6632       case IcsIdle:
6633         /* We switched into a game mode where moves are not accepted,
6634            perhaps while the mouse button was down. */
6635         return;
6636
6637       case MachinePlaysWhite:
6638         /* User is moving for Black */
6639         if (WhiteOnMove(currentMove)) {
6640             DisplayMoveError(_("It is White's turn"));
6641             return;
6642         }
6643         break;
6644
6645       case MachinePlaysBlack:
6646         /* User is moving for White */
6647         if (!WhiteOnMove(currentMove)) {
6648             DisplayMoveError(_("It is Black's turn"));
6649             return;
6650         }
6651         break;
6652
6653       case PlayFromGameFile:
6654             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6655       case EditGame:
6656       case IcsExamining:
6657       case BeginningOfGame:
6658       case AnalyzeMode:
6659       case Training:
6660         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6661         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6662             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6663             /* User is moving for Black */
6664             if (WhiteOnMove(currentMove)) {
6665                 DisplayMoveError(_("It is White's turn"));
6666                 return;
6667             }
6668         } else {
6669             /* User is moving for White */
6670             if (!WhiteOnMove(currentMove)) {
6671                 DisplayMoveError(_("It is Black's turn"));
6672                 return;
6673             }
6674         }
6675         break;
6676
6677       case IcsPlayingBlack:
6678         /* User is moving for Black */
6679         if (WhiteOnMove(currentMove)) {
6680             if (!appData.premove) {
6681                 DisplayMoveError(_("It is White's turn"));
6682             } else if (toX >= 0 && toY >= 0) {
6683                 premoveToX = toX;
6684                 premoveToY = toY;
6685                 premoveFromX = fromX;
6686                 premoveFromY = fromY;
6687                 premovePromoChar = promoChar;
6688                 gotPremove = 1;
6689                 if (appData.debugMode)
6690                     fprintf(debugFP, "Got premove: fromX %d,"
6691                             "fromY %d, toX %d, toY %d\n",
6692                             fromX, fromY, toX, toY);
6693             }
6694             return;
6695         }
6696         break;
6697
6698       case IcsPlayingWhite:
6699         /* User is moving for White */
6700         if (!WhiteOnMove(currentMove)) {
6701             if (!appData.premove) {
6702                 DisplayMoveError(_("It is Black's turn"));
6703             } else if (toX >= 0 && toY >= 0) {
6704                 premoveToX = toX;
6705                 premoveToY = toY;
6706                 premoveFromX = fromX;
6707                 premoveFromY = fromY;
6708                 premovePromoChar = promoChar;
6709                 gotPremove = 1;
6710                 if (appData.debugMode)
6711                     fprintf(debugFP, "Got premove: fromX %d,"
6712                             "fromY %d, toX %d, toY %d\n",
6713                             fromX, fromY, toX, toY);
6714             }
6715             return;
6716         }
6717         break;
6718
6719       default:
6720         break;
6721
6722       case EditPosition:
6723         /* EditPosition, empty square, or different color piece;
6724            click-click move is possible */
6725         if (toX == -2 || toY == -2) {
6726             boards[0][fromY][fromX] = EmptySquare;
6727             DrawPosition(FALSE, boards[currentMove]);
6728             return;
6729         } else if (toX >= 0 && toY >= 0) {
6730             boards[0][toY][toX] = boards[0][fromY][fromX];
6731             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6732                 if(boards[0][fromY][0] != EmptySquare) {
6733                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6734                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6735                 }
6736             } else
6737             if(fromX == BOARD_RGHT+1) {
6738                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6739                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6740                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6741                 }
6742             } else
6743             boards[0][fromY][fromX] = gatingPiece;
6744             DrawPosition(FALSE, boards[currentMove]);
6745             return;
6746         }
6747         return;
6748     }
6749
6750     if(toX < 0 || toY < 0) return;
6751     pup = boards[currentMove][toY][toX];
6752
6753     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6754     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6755          if( pup != EmptySquare ) return;
6756          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6757            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6758                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6759            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6760            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6761            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6762            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6763          fromY = DROP_RANK;
6764     }
6765
6766     /* [HGM] always test for legality, to get promotion info */
6767     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6768                                          fromY, fromX, toY, toX, promoChar);
6769
6770     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6771
6772     /* [HGM] but possibly ignore an IllegalMove result */
6773     if (appData.testLegality) {
6774         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6775             DisplayMoveError(_("Illegal move"));
6776             return;
6777         }
6778     }
6779
6780     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6781         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6782              ClearPremoveHighlights(); // was included
6783         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6784         return;
6785     }
6786
6787     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6788 }
6789
6790 /* Common tail of UserMoveEvent and DropMenuEvent */
6791 int
6792 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6793 {
6794     char *bookHit = 0;
6795
6796     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6797         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6798         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6799         if(WhiteOnMove(currentMove)) {
6800             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6801         } else {
6802             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6803         }
6804     }
6805
6806     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6807        move type in caller when we know the move is a legal promotion */
6808     if(moveType == NormalMove && promoChar)
6809         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6810
6811     /* [HGM] <popupFix> The following if has been moved here from
6812        UserMoveEvent(). Because it seemed to belong here (why not allow
6813        piece drops in training games?), and because it can only be
6814        performed after it is known to what we promote. */
6815     if (gameMode == Training) {
6816       /* compare the move played on the board to the next move in the
6817        * game. If they match, display the move and the opponent's response.
6818        * If they don't match, display an error message.
6819        */
6820       int saveAnimate;
6821       Board testBoard;
6822       CopyBoard(testBoard, boards[currentMove]);
6823       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6824
6825       if (CompareBoards(testBoard, boards[currentMove+1])) {
6826         ForwardInner(currentMove+1);
6827
6828         /* Autoplay the opponent's response.
6829          * if appData.animate was TRUE when Training mode was entered,
6830          * the response will be animated.
6831          */
6832         saveAnimate = appData.animate;
6833         appData.animate = animateTraining;
6834         ForwardInner(currentMove+1);
6835         appData.animate = saveAnimate;
6836
6837         /* check for the end of the game */
6838         if (currentMove >= forwardMostMove) {
6839           gameMode = PlayFromGameFile;
6840           ModeHighlight();
6841           SetTrainingModeOff();
6842           DisplayInformation(_("End of game"));
6843         }
6844       } else {
6845         DisplayError(_("Incorrect move"), 0);
6846       }
6847       return 1;
6848     }
6849
6850   /* Ok, now we know that the move is good, so we can kill
6851      the previous line in Analysis Mode */
6852   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6853                                 && currentMove < forwardMostMove) {
6854     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6855     else forwardMostMove = currentMove;
6856   }
6857
6858   ClearMap();
6859
6860   /* If we need the chess program but it's dead, restart it */
6861   ResurrectChessProgram();
6862
6863   /* A user move restarts a paused game*/
6864   if (pausing)
6865     PauseEvent();
6866
6867   thinkOutput[0] = NULLCHAR;
6868
6869   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6870
6871   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6872     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6873     return 1;
6874   }
6875
6876   if (gameMode == BeginningOfGame) {
6877     if (appData.noChessProgram) {
6878       gameMode = EditGame;
6879       SetGameInfo();
6880     } else {
6881       char buf[MSG_SIZ];
6882       gameMode = MachinePlaysBlack;
6883       StartClocks();
6884       SetGameInfo();
6885       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6886       DisplayTitle(buf);
6887       if (first.sendName) {
6888         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6889         SendToProgram(buf, &first);
6890       }
6891       StartClocks();
6892     }
6893     ModeHighlight();
6894   }
6895
6896   /* Relay move to ICS or chess engine */
6897   if (appData.icsActive) {
6898     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6899         gameMode == IcsExamining) {
6900       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6901         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6902         SendToICS("draw ");
6903         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6904       }
6905       // also send plain move, in case ICS does not understand atomic claims
6906       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6907       ics_user_moved = 1;
6908     }
6909   } else {
6910     if (first.sendTime && (gameMode == BeginningOfGame ||
6911                            gameMode == MachinePlaysWhite ||
6912                            gameMode == MachinePlaysBlack)) {
6913       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6914     }
6915     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6916          // [HGM] book: if program might be playing, let it use book
6917         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6918         first.maybeThinking = TRUE;
6919     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6920         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6921         SendBoard(&first, currentMove+1);
6922         if(second.analyzing) {
6923             if(!second.useSetboard) SendToProgram("undo\n", &second);
6924             SendBoard(&second, currentMove+1);
6925         }
6926     } else {
6927         SendMoveToProgram(forwardMostMove-1, &first);
6928         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6929     }
6930     if (currentMove == cmailOldMove + 1) {
6931       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6932     }
6933   }
6934
6935   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6936
6937   switch (gameMode) {
6938   case EditGame:
6939     if(appData.testLegality)
6940     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6941     case MT_NONE:
6942     case MT_CHECK:
6943       break;
6944     case MT_CHECKMATE:
6945     case MT_STAINMATE:
6946       if (WhiteOnMove(currentMove)) {
6947         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6948       } else {
6949         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6950       }
6951       break;
6952     case MT_STALEMATE:
6953       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6954       break;
6955     }
6956     break;
6957
6958   case MachinePlaysBlack:
6959   case MachinePlaysWhite:
6960     /* disable certain menu options while machine is thinking */
6961     SetMachineThinkingEnables();
6962     break;
6963
6964   default:
6965     break;
6966   }
6967
6968   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6969   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6970
6971   if(bookHit) { // [HGM] book: simulate book reply
6972         static char bookMove[MSG_SIZ]; // a bit generous?
6973
6974         programStats.nodes = programStats.depth = programStats.time =
6975         programStats.score = programStats.got_only_move = 0;
6976         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6977
6978         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6979         strcat(bookMove, bookHit);
6980         HandleMachineMove(bookMove, &first);
6981   }
6982   return 1;
6983 }
6984
6985 void
6986 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6987 {
6988     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6989     Markers *m = (Markers *) closure;
6990     if(rf == fromY && ff == fromX)
6991         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6992                          || kind == WhiteCapturesEnPassant
6993                          || kind == BlackCapturesEnPassant);
6994     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6995 }
6996
6997 void
6998 MarkTargetSquares (int clear)
6999 {
7000   int x, y;
7001   if(clear) // no reason to ever suppress clearing
7002     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7003   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7004      !appData.testLegality || gameMode == EditPosition) return;
7005   if(!clear) {
7006     int capt = 0;
7007     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7008     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7009       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7010       if(capt)
7011       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7012     }
7013   }
7014   DrawPosition(FALSE, NULL);
7015 }
7016
7017 int
7018 Explode (Board board, int fromX, int fromY, int toX, int toY)
7019 {
7020     if(gameInfo.variant == VariantAtomic &&
7021        (board[toY][toX] != EmptySquare ||                     // capture?
7022         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7023                          board[fromY][fromX] == BlackPawn   )
7024       )) {
7025         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7026         return TRUE;
7027     }
7028     return FALSE;
7029 }
7030
7031 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7032
7033 int
7034 CanPromote (ChessSquare piece, int y)
7035 {
7036         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7037         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7038         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7039            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7040            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7041                                                   gameInfo.variant == VariantMakruk) return FALSE;
7042         return (piece == BlackPawn && y == 1 ||
7043                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7044                 piece == BlackLance && y == 1 ||
7045                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7046 }
7047
7048 void
7049 LeftClick (ClickType clickType, int xPix, int yPix)
7050 {
7051     int x, y;
7052     Boolean saveAnimate;
7053     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7054     char promoChoice = NULLCHAR;
7055     ChessSquare piece;
7056     static TimeMark lastClickTime, prevClickTime;
7057
7058     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7059
7060     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7061
7062     if (clickType == Press) ErrorPopDown();
7063
7064     x = EventToSquare(xPix, BOARD_WIDTH);
7065     y = EventToSquare(yPix, BOARD_HEIGHT);
7066     if (!flipView && y >= 0) {
7067         y = BOARD_HEIGHT - 1 - y;
7068     }
7069     if (flipView && x >= 0) {
7070         x = BOARD_WIDTH - 1 - x;
7071     }
7072
7073     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7074         defaultPromoChoice = promoSweep;
7075         promoSweep = EmptySquare;   // terminate sweep
7076         promoDefaultAltered = TRUE;
7077         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7078     }
7079
7080     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7081         if(clickType == Release) return; // ignore upclick of click-click destination
7082         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7083         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7084         if(gameInfo.holdingsWidth &&
7085                 (WhiteOnMove(currentMove)
7086                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7087                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7088             // click in right holdings, for determining promotion piece
7089             ChessSquare p = boards[currentMove][y][x];
7090             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7091             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7092             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7093                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7094                 fromX = fromY = -1;
7095                 return;
7096             }
7097         }
7098         DrawPosition(FALSE, boards[currentMove]);
7099         return;
7100     }
7101
7102     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7103     if(clickType == Press
7104             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7105               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7106               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7107         return;
7108
7109     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7110         // could be static click on premove from-square: abort premove
7111         gotPremove = 0;
7112         ClearPremoveHighlights();
7113     }
7114
7115     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7116         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7117
7118     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7119         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7120                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7121         defaultPromoChoice = DefaultPromoChoice(side);
7122     }
7123
7124     autoQueen = appData.alwaysPromoteToQueen;
7125
7126     if (fromX == -1) {
7127       int originalY = y;
7128       gatingPiece = EmptySquare;
7129       if (clickType != Press) {
7130         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7131             DragPieceEnd(xPix, yPix); dragging = 0;
7132             DrawPosition(FALSE, NULL);
7133         }
7134         return;
7135       }
7136       doubleClick = FALSE;
7137       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7138         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7139       }
7140       fromX = x; fromY = y; toX = toY = -1;
7141       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7142          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7143          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7144             /* First square */
7145             if (OKToStartUserMove(fromX, fromY)) {
7146                 second = 0;
7147                 MarkTargetSquares(0);
7148                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7149                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7150                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7151                     promoSweep = defaultPromoChoice;
7152                     selectFlag = 0; lastX = xPix; lastY = yPix;
7153                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7154                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7155                 }
7156                 if (appData.highlightDragging) {
7157                     SetHighlights(fromX, fromY, -1, -1);
7158                 } else {
7159                     ClearHighlights();
7160                 }
7161             } else fromX = fromY = -1;
7162             return;
7163         }
7164     }
7165
7166     /* fromX != -1 */
7167     if (clickType == Press && gameMode != EditPosition) {
7168         ChessSquare fromP;
7169         ChessSquare toP;
7170         int frc;
7171
7172         // ignore off-board to clicks
7173         if(y < 0 || x < 0) return;
7174
7175         /* Check if clicking again on the same color piece */
7176         fromP = boards[currentMove][fromY][fromX];
7177         toP = boards[currentMove][y][x];
7178         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7179         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7180              WhitePawn <= toP && toP <= WhiteKing &&
7181              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7182              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7183             (BlackPawn <= fromP && fromP <= BlackKing &&
7184              BlackPawn <= toP && toP <= BlackKing &&
7185              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7186              !(fromP == BlackKing && toP == BlackRook && frc))) {
7187             /* Clicked again on same color piece -- changed his mind */
7188             second = (x == fromX && y == fromY);
7189             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7190                 second = FALSE; // first double-click rather than scond click
7191                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7192             }
7193             promoDefaultAltered = FALSE;
7194             MarkTargetSquares(1);
7195            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7196             if (appData.highlightDragging) {
7197                 SetHighlights(x, y, -1, -1);
7198             } else {
7199                 ClearHighlights();
7200             }
7201             if (OKToStartUserMove(x, y)) {
7202                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7203                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7204                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7205                  gatingPiece = boards[currentMove][fromY][fromX];
7206                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7207                 fromX = x;
7208                 fromY = y; dragging = 1;
7209                 MarkTargetSquares(0);
7210                 DragPieceBegin(xPix, yPix, FALSE);
7211                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7212                     promoSweep = defaultPromoChoice;
7213                     selectFlag = 0; lastX = xPix; lastY = yPix;
7214                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7215                 }
7216             }
7217            }
7218            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7219            second = FALSE;
7220         }
7221         // ignore clicks on holdings
7222         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7223     }
7224
7225     if (clickType == Release && x == fromX && y == fromY) {
7226         DragPieceEnd(xPix, yPix); dragging = 0;
7227         if(clearFlag) {
7228             // a deferred attempt to click-click move an empty square on top of a piece
7229             boards[currentMove][y][x] = EmptySquare;
7230             ClearHighlights();
7231             DrawPosition(FALSE, boards[currentMove]);
7232             fromX = fromY = -1; clearFlag = 0;
7233             return;
7234         }
7235         if (appData.animateDragging) {
7236             /* Undo animation damage if any */
7237             DrawPosition(FALSE, NULL);
7238         }
7239         if (second || sweepSelecting) {
7240             /* Second up/down in same square; just abort move */
7241             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7242             second = sweepSelecting = 0;
7243             fromX = fromY = -1;
7244             gatingPiece = EmptySquare;
7245             ClearHighlights();
7246             gotPremove = 0;
7247             ClearPremoveHighlights();
7248         } else {
7249             /* First upclick in same square; start click-click mode */
7250             SetHighlights(x, y, -1, -1);
7251         }
7252         return;
7253     }
7254
7255     clearFlag = 0;
7256
7257     /* we now have a different from- and (possibly off-board) to-square */
7258     /* Completed move */
7259     if(!sweepSelecting) {
7260         toX = x;
7261         toY = y;
7262     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7263
7264     saveAnimate = appData.animate;
7265     if (clickType == Press) {
7266         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7267             // must be Edit Position mode with empty-square selected
7268             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7269             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7270             return;
7271         }
7272         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7273           if(appData.sweepSelect) {
7274             ChessSquare piece = boards[currentMove][fromY][fromX];
7275             promoSweep = defaultPromoChoice;
7276             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7277             selectFlag = 0; lastX = xPix; lastY = yPix;
7278             Sweep(0); // Pawn that is going to promote: preview promotion piece
7279             sweepSelecting = 1;
7280             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7281             MarkTargetSquares(1);
7282           }
7283           return; // promo popup appears on up-click
7284         }
7285         /* Finish clickclick move */
7286         if (appData.animate || appData.highlightLastMove) {
7287             SetHighlights(fromX, fromY, toX, toY);
7288         } else {
7289             ClearHighlights();
7290         }
7291     } else {
7292 #if 0
7293 // [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
7294         /* Finish drag move */
7295         if (appData.highlightLastMove) {
7296             SetHighlights(fromX, fromY, toX, toY);
7297         } else {
7298             ClearHighlights();
7299         }
7300 #endif
7301         DragPieceEnd(xPix, yPix); dragging = 0;
7302         /* Don't animate move and drag both */
7303         appData.animate = FALSE;
7304     }
7305
7306     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7307     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7308         ChessSquare piece = boards[currentMove][fromY][fromX];
7309         if(gameMode == EditPosition && piece != EmptySquare &&
7310            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7311             int n;
7312
7313             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7314                 n = PieceToNumber(piece - (int)BlackPawn);
7315                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7316                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7317                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7318             } else
7319             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7320                 n = PieceToNumber(piece);
7321                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7322                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7323                 boards[currentMove][n][BOARD_WIDTH-2]++;
7324             }
7325             boards[currentMove][fromY][fromX] = EmptySquare;
7326         }
7327         ClearHighlights();
7328         fromX = fromY = -1;
7329         MarkTargetSquares(1);
7330         DrawPosition(TRUE, boards[currentMove]);
7331         return;
7332     }
7333
7334     // off-board moves should not be highlighted
7335     if(x < 0 || y < 0) ClearHighlights();
7336
7337     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7338
7339     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7340         SetHighlights(fromX, fromY, toX, toY);
7341         MarkTargetSquares(1);
7342         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7343             // [HGM] super: promotion to captured piece selected from holdings
7344             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7345             promotionChoice = TRUE;
7346             // kludge follows to temporarily execute move on display, without promoting yet
7347             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7348             boards[currentMove][toY][toX] = p;
7349             DrawPosition(FALSE, boards[currentMove]);
7350             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7351             boards[currentMove][toY][toX] = q;
7352             DisplayMessage("Click in holdings to choose piece", "");
7353             return;
7354         }
7355         PromotionPopUp();
7356     } else {
7357         int oldMove = currentMove;
7358         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7359         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7360         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7361         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7362            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7363             DrawPosition(TRUE, boards[currentMove]);
7364         MarkTargetSquares(1);
7365         fromX = fromY = -1;
7366     }
7367     appData.animate = saveAnimate;
7368     if (appData.animate || appData.animateDragging) {
7369         /* Undo animation damage if needed */
7370         DrawPosition(FALSE, NULL);
7371     }
7372 }
7373
7374 int
7375 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7376 {   // front-end-free part taken out of PieceMenuPopup
7377     int whichMenu; int xSqr, ySqr;
7378
7379     if(seekGraphUp) { // [HGM] seekgraph
7380         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7381         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7382         return -2;
7383     }
7384
7385     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7386          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7387         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7388         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7389         if(action == Press)   {
7390             originalFlip = flipView;
7391             flipView = !flipView; // temporarily flip board to see game from partners perspective
7392             DrawPosition(TRUE, partnerBoard);
7393             DisplayMessage(partnerStatus, "");
7394             partnerUp = TRUE;
7395         } else if(action == Release) {
7396             flipView = originalFlip;
7397             DrawPosition(TRUE, boards[currentMove]);
7398             partnerUp = FALSE;
7399         }
7400         return -2;
7401     }
7402
7403     xSqr = EventToSquare(x, BOARD_WIDTH);
7404     ySqr = EventToSquare(y, BOARD_HEIGHT);
7405     if (action == Release) {
7406         if(pieceSweep != EmptySquare) {
7407             EditPositionMenuEvent(pieceSweep, toX, toY);
7408             pieceSweep = EmptySquare;
7409         } else UnLoadPV(); // [HGM] pv
7410     }
7411     if (action != Press) return -2; // return code to be ignored
7412     switch (gameMode) {
7413       case IcsExamining:
7414         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7415       case EditPosition:
7416         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7417         if (xSqr < 0 || ySqr < 0) return -1;
7418         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7419         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7420         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7421         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7422         NextPiece(0);
7423         return 2; // grab
7424       case IcsObserving:
7425         if(!appData.icsEngineAnalyze) return -1;
7426       case IcsPlayingWhite:
7427       case IcsPlayingBlack:
7428         if(!appData.zippyPlay) goto noZip;
7429       case AnalyzeMode:
7430       case AnalyzeFile:
7431       case MachinePlaysWhite:
7432       case MachinePlaysBlack:
7433       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7434         if (!appData.dropMenu) {
7435           LoadPV(x, y);
7436           return 2; // flag front-end to grab mouse events
7437         }
7438         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7439            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7440       case EditGame:
7441       noZip:
7442         if (xSqr < 0 || ySqr < 0) return -1;
7443         if (!appData.dropMenu || appData.testLegality &&
7444             gameInfo.variant != VariantBughouse &&
7445             gameInfo.variant != VariantCrazyhouse) return -1;
7446         whichMenu = 1; // drop menu
7447         break;
7448       default:
7449         return -1;
7450     }
7451
7452     if (((*fromX = xSqr) < 0) ||
7453         ((*fromY = ySqr) < 0)) {
7454         *fromX = *fromY = -1;
7455         return -1;
7456     }
7457     if (flipView)
7458       *fromX = BOARD_WIDTH - 1 - *fromX;
7459     else
7460       *fromY = BOARD_HEIGHT - 1 - *fromY;
7461
7462     return whichMenu;
7463 }
7464
7465 void
7466 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7467 {
7468 //    char * hint = lastHint;
7469     FrontEndProgramStats stats;
7470
7471     stats.which = cps == &first ? 0 : 1;
7472     stats.depth = cpstats->depth;
7473     stats.nodes = cpstats->nodes;
7474     stats.score = cpstats->score;
7475     stats.time = cpstats->time;
7476     stats.pv = cpstats->movelist;
7477     stats.hint = lastHint;
7478     stats.an_move_index = 0;
7479     stats.an_move_count = 0;
7480
7481     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7482         stats.hint = cpstats->move_name;
7483         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7484         stats.an_move_count = cpstats->nr_moves;
7485     }
7486
7487     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
7488
7489     SetProgramStats( &stats );
7490 }
7491
7492 void
7493 ClearEngineOutputPane (int which)
7494 {
7495     static FrontEndProgramStats dummyStats;
7496     dummyStats.which = which;
7497     dummyStats.pv = "#";
7498     SetProgramStats( &dummyStats );
7499 }
7500
7501 #define MAXPLAYERS 500
7502
7503 char *
7504 TourneyStandings (int display)
7505 {
7506     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7507     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7508     char result, *p, *names[MAXPLAYERS];
7509
7510     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7511         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7512     names[0] = p = strdup(appData.participants);
7513     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7514
7515     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7516
7517     while(result = appData.results[nr]) {
7518         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7519         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7520         wScore = bScore = 0;
7521         switch(result) {
7522           case '+': wScore = 2; break;
7523           case '-': bScore = 2; break;
7524           case '=': wScore = bScore = 1; break;
7525           case ' ':
7526           case '*': return strdup("busy"); // tourney not finished
7527         }
7528         score[w] += wScore;
7529         score[b] += bScore;
7530         games[w]++;
7531         games[b]++;
7532         nr++;
7533     }
7534     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7535     for(w=0; w<nPlayers; w++) {
7536         bScore = -1;
7537         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7538         ranking[w] = b; points[w] = bScore; score[b] = -2;
7539     }
7540     p = malloc(nPlayers*34+1);
7541     for(w=0; w<nPlayers && w<display; w++)
7542         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7543     free(names[0]);
7544     return p;
7545 }
7546
7547 void
7548 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7549 {       // count all piece types
7550         int p, f, r;
7551         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7552         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7553         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7554                 p = board[r][f];
7555                 pCnt[p]++;
7556                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7557                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7558                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7559                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7560                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7561                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7562         }
7563 }
7564
7565 int
7566 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7567 {
7568         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7569         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7570
7571         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7572         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7573         if(myPawns == 2 && nMine == 3) // KPP
7574             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7575         if(myPawns == 1 && nMine == 2) // KP
7576             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7577         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7578             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7579         if(myPawns) return FALSE;
7580         if(pCnt[WhiteRook+side])
7581             return pCnt[BlackRook-side] ||
7582                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7583                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7584                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7585         if(pCnt[WhiteCannon+side]) {
7586             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7587             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7588         }
7589         if(pCnt[WhiteKnight+side])
7590             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7591         return FALSE;
7592 }
7593
7594 int
7595 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7596 {
7597         VariantClass v = gameInfo.variant;
7598
7599         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7600         if(v == VariantShatranj) return TRUE; // always winnable through baring
7601         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7602         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7603
7604         if(v == VariantXiangqi) {
7605                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7606
7607                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7608                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7609                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7610                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7611                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7612                 if(stale) // we have at least one last-rank P plus perhaps C
7613                     return majors // KPKX
7614                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7615                 else // KCA*E*
7616                     return pCnt[WhiteFerz+side] // KCAK
7617                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7618                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7619                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7620
7621         } else if(v == VariantKnightmate) {
7622                 if(nMine == 1) return FALSE;
7623                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7624         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7625                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7626
7627                 if(nMine == 1) return FALSE; // bare King
7628                 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
7629                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7630                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7631                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7632                 if(pCnt[WhiteKnight+side])
7633                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7634                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7635                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7636                 if(nBishops)
7637                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7638                 if(pCnt[WhiteAlfil+side])
7639                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7640                 if(pCnt[WhiteWazir+side])
7641                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7642         }
7643
7644         return TRUE;
7645 }
7646
7647 int
7648 CompareWithRights (Board b1, Board b2)
7649 {
7650     int rights = 0;
7651     if(!CompareBoards(b1, b2)) return FALSE;
7652     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7653     /* compare castling rights */
7654     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7655            rights++; /* King lost rights, while rook still had them */
7656     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7657         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7658            rights++; /* but at least one rook lost them */
7659     }
7660     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7661            rights++;
7662     if( b1[CASTLING][5] != NoRights ) {
7663         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7664            rights++;
7665     }
7666     return rights == 0;
7667 }
7668
7669 int
7670 Adjudicate (ChessProgramState *cps)
7671 {       // [HGM] some adjudications useful with buggy engines
7672         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7673         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7674         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7675         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7676         int k, drop, count = 0; static int bare = 1;
7677         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7678         Boolean canAdjudicate = !appData.icsActive;
7679
7680         // most tests only when we understand the game, i.e. legality-checking on
7681             if( appData.testLegality )
7682             {   /* [HGM] Some more adjudications for obstinate engines */
7683                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7684                 static int moveCount = 6;
7685                 ChessMove result;
7686                 char *reason = NULL;
7687
7688                 /* Count what is on board. */
7689                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7690
7691                 /* Some material-based adjudications that have to be made before stalemate test */
7692                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7693                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7694                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7695                      if(canAdjudicate && appData.checkMates) {
7696                          if(engineOpponent)
7697                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7698                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7699                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7700                          return 1;
7701                      }
7702                 }
7703
7704                 /* Bare King in Shatranj (loses) or Losers (wins) */
7705                 if( nrW == 1 || nrB == 1) {
7706                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7707                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7708                      if(canAdjudicate && appData.checkMates) {
7709                          if(engineOpponent)
7710                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7711                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7712                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7713                          return 1;
7714                      }
7715                   } else
7716                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7717                   {    /* bare King */
7718                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7719                         if(canAdjudicate && appData.checkMates) {
7720                             /* but only adjudicate if adjudication enabled */
7721                             if(engineOpponent)
7722                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7723                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7724                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7725                             return 1;
7726                         }
7727                   }
7728                 } else bare = 1;
7729
7730
7731             // don't wait for engine to announce game end if we can judge ourselves
7732             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7733               case MT_CHECK:
7734                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7735                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7736                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7737                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7738                             checkCnt++;
7739                         if(checkCnt >= 2) {
7740                             reason = "Xboard adjudication: 3rd check";
7741                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7742                             break;
7743                         }
7744                     }
7745                 }
7746               case MT_NONE:
7747               default:
7748                 break;
7749               case MT_STALEMATE:
7750               case MT_STAINMATE:
7751                 reason = "Xboard adjudication: Stalemate";
7752                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7753                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7754                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7755                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7756                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7757                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7758                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7759                                                                         EP_CHECKMATE : EP_WINS);
7760                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7761                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7762                 }
7763                 break;
7764               case MT_CHECKMATE:
7765                 reason = "Xboard adjudication: Checkmate";
7766                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7767                 break;
7768             }
7769
7770                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7771                     case EP_STALEMATE:
7772                         result = GameIsDrawn; break;
7773                     case EP_CHECKMATE:
7774                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7775                     case EP_WINS:
7776                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7777                     default:
7778                         result = EndOfFile;
7779                 }
7780                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7781                     if(engineOpponent)
7782                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7783                     GameEnds( result, reason, GE_XBOARD );
7784                     return 1;
7785                 }
7786
7787                 /* Next absolutely insufficient mating material. */
7788                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7789                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7790                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7791
7792                      /* always flag draws, for judging claims */
7793                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7794
7795                      if(canAdjudicate && appData.materialDraws) {
7796                          /* but only adjudicate them if adjudication enabled */
7797                          if(engineOpponent) {
7798                            SendToProgram("force\n", engineOpponent); // suppress reply
7799                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7800                          }
7801                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7802                          return 1;
7803                      }
7804                 }
7805
7806                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7807                 if(gameInfo.variant == VariantXiangqi ?
7808                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7809                  : nrW + nrB == 4 &&
7810                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7811                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7812                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7813                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7814                    ) ) {
7815                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7816                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7817                           if(engineOpponent) {
7818                             SendToProgram("force\n", engineOpponent); // suppress reply
7819                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7820                           }
7821                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7822                           return 1;
7823                      }
7824                 } else moveCount = 6;
7825             }
7826
7827         // Repetition draws and 50-move rule can be applied independently of legality testing
7828
7829                 /* Check for rep-draws */
7830                 count = 0;
7831                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7832                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7833                 for(k = forwardMostMove-2;
7834                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7835                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7836                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7837                     k-=2)
7838                 {   int rights=0;
7839                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7840                         /* compare castling rights */
7841                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7842                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7843                                 rights++; /* King lost rights, while rook still had them */
7844                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7845                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7846                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7847                                    rights++; /* but at least one rook lost them */
7848                         }
7849                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7850                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7851                                 rights++;
7852                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7853                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7854                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7855                                    rights++;
7856                         }
7857                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7858                             && appData.drawRepeats > 1) {
7859                              /* adjudicate after user-specified nr of repeats */
7860                              int result = GameIsDrawn;
7861                              char *details = "XBoard adjudication: repetition draw";
7862                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7863                                 // [HGM] xiangqi: check for forbidden perpetuals
7864                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7865                                 for(m=forwardMostMove; m>k; m-=2) {
7866                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7867                                         ourPerpetual = 0; // the current mover did not always check
7868                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7869                                         hisPerpetual = 0; // the opponent did not always check
7870                                 }
7871                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7872                                                                         ourPerpetual, hisPerpetual);
7873                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7874                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7875                                     details = "Xboard adjudication: perpetual checking";
7876                                 } else
7877                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7878                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7879                                 } else
7880                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7881                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7882                                         result = BlackWins;
7883                                         details = "Xboard adjudication: repetition";
7884                                     }
7885                                 } else // it must be XQ
7886                                 // Now check for perpetual chases
7887                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7888                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7889                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7890                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7891                                         static char resdet[MSG_SIZ];
7892                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7893                                         details = resdet;
7894                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7895                                     } else
7896                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7897                                         break; // Abort repetition-checking loop.
7898                                 }
7899                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7900                              }
7901                              if(engineOpponent) {
7902                                SendToProgram("force\n", engineOpponent); // suppress reply
7903                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7904                              }
7905                              GameEnds( result, details, GE_XBOARD );
7906                              return 1;
7907                         }
7908                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7909                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7910                     }
7911                 }
7912
7913                 /* Now we test for 50-move draws. Determine ply count */
7914                 count = forwardMostMove;
7915                 /* look for last irreversble move */
7916                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7917                     count--;
7918                 /* if we hit starting position, add initial plies */
7919                 if( count == backwardMostMove )
7920                     count -= initialRulePlies;
7921                 count = forwardMostMove - count;
7922                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7923                         // adjust reversible move counter for checks in Xiangqi
7924                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7925                         if(i < backwardMostMove) i = backwardMostMove;
7926                         while(i <= forwardMostMove) {
7927                                 lastCheck = inCheck; // check evasion does not count
7928                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7929                                 if(inCheck || lastCheck) count--; // check does not count
7930                                 i++;
7931                         }
7932                 }
7933                 if( count >= 100)
7934                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7935                          /* this is used to judge if draw claims are legal */
7936                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7937                          if(engineOpponent) {
7938                            SendToProgram("force\n", engineOpponent); // suppress reply
7939                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7940                          }
7941                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7942                          return 1;
7943                 }
7944
7945                 /* if draw offer is pending, treat it as a draw claim
7946                  * when draw condition present, to allow engines a way to
7947                  * claim draws before making their move to avoid a race
7948                  * condition occurring after their move
7949                  */
7950                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7951                          char *p = NULL;
7952                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7953                              p = "Draw claim: 50-move rule";
7954                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7955                              p = "Draw claim: 3-fold repetition";
7956                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7957                              p = "Draw claim: insufficient mating material";
7958                          if( p != NULL && canAdjudicate) {
7959                              if(engineOpponent) {
7960                                SendToProgram("force\n", engineOpponent); // suppress reply
7961                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7962                              }
7963                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7964                              return 1;
7965                          }
7966                 }
7967
7968                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7969                     if(engineOpponent) {
7970                       SendToProgram("force\n", engineOpponent); // suppress reply
7971                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7972                     }
7973                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7974                     return 1;
7975                 }
7976         return 0;
7977 }
7978
7979 char *
7980 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7981 {   // [HGM] book: this routine intercepts moves to simulate book replies
7982     char *bookHit = NULL;
7983
7984     //first determine if the incoming move brings opponent into his book
7985     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7986         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7987     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7988     if(bookHit != NULL && !cps->bookSuspend) {
7989         // make sure opponent is not going to reply after receiving move to book position
7990         SendToProgram("force\n", cps);
7991         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7992     }
7993     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7994     // now arrange restart after book miss
7995     if(bookHit) {
7996         // after a book hit we never send 'go', and the code after the call to this routine
7997         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7998         char buf[MSG_SIZ], *move = bookHit;
7999         if(cps->useSAN) {
8000             int fromX, fromY, toX, toY;
8001             char promoChar;
8002             ChessMove moveType;
8003             move = buf + 30;
8004             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8005                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8006                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8007                                     PosFlags(forwardMostMove),
8008                                     fromY, fromX, toY, toX, promoChar, move);
8009             } else {
8010                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8011                 bookHit = NULL;
8012             }
8013         }
8014         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8015         SendToProgram(buf, cps);
8016         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8017     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8018         SendToProgram("go\n", cps);
8019         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8020     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8021         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8022             SendToProgram("go\n", cps);
8023         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8024     }
8025     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8026 }
8027
8028 int
8029 LoadError (char *errmess, ChessProgramState *cps)
8030 {   // unloads engine and switches back to -ncp mode if it was first
8031     if(cps->initDone) return FALSE;
8032     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8033     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8034     cps->pr = NoProc;
8035     if(cps == &first) {
8036         appData.noChessProgram = TRUE;
8037         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8038         gameMode = BeginningOfGame; ModeHighlight();
8039         SetNCPMode();
8040     }
8041     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8042     DisplayMessage("", ""); // erase waiting message
8043     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8044     return TRUE;
8045 }
8046
8047 char *savedMessage;
8048 ChessProgramState *savedState;
8049 void
8050 DeferredBookMove (void)
8051 {
8052         if(savedState->lastPing != savedState->lastPong)
8053                     ScheduleDelayedEvent(DeferredBookMove, 10);
8054         else
8055         HandleMachineMove(savedMessage, savedState);
8056 }
8057
8058 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8059 static ChessProgramState *stalledEngine;
8060 static char stashedInputMove[MSG_SIZ];
8061
8062 void
8063 HandleMachineMove (char *message, ChessProgramState *cps)
8064 {
8065     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8066     char realname[MSG_SIZ];
8067     int fromX, fromY, toX, toY;
8068     ChessMove moveType;
8069     char promoChar;
8070     char *p, *pv=buf1;
8071     int machineWhite, oldError;
8072     char *bookHit;
8073
8074     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8075         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8076         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8077             DisplayError(_("Invalid pairing from pairing engine"), 0);
8078             return;
8079         }
8080         pairingReceived = 1;
8081         NextMatchGame();
8082         return; // Skim the pairing messages here.
8083     }
8084
8085     oldError = cps->userError; cps->userError = 0;
8086
8087 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8088     /*
8089      * Kludge to ignore BEL characters
8090      */
8091     while (*message == '\007') message++;
8092
8093     /*
8094      * [HGM] engine debug message: ignore lines starting with '#' character
8095      */
8096     if(cps->debug && *message == '#') return;
8097
8098     /*
8099      * Look for book output
8100      */
8101     if (cps == &first && bookRequested) {
8102         if (message[0] == '\t' || message[0] == ' ') {
8103             /* Part of the book output is here; append it */
8104             strcat(bookOutput, message);
8105             strcat(bookOutput, "  \n");
8106             return;
8107         } else if (bookOutput[0] != NULLCHAR) {
8108             /* All of book output has arrived; display it */
8109             char *p = bookOutput;
8110             while (*p != NULLCHAR) {
8111                 if (*p == '\t') *p = ' ';
8112                 p++;
8113             }
8114             DisplayInformation(bookOutput);
8115             bookRequested = FALSE;
8116             /* Fall through to parse the current output */
8117         }
8118     }
8119
8120     /*
8121      * Look for machine move.
8122      */
8123     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8124         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8125     {
8126         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8127             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8128             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8129             stalledEngine = cps;
8130             if(appData.ponderNextMove) { // bring opponent out of ponder
8131                 if(gameMode == TwoMachinesPlay) {
8132                     if(cps->other->pause)
8133                         PauseEngine(cps->other);
8134                     else
8135                         SendToProgram("easy\n", cps->other);
8136                 }
8137             }
8138             StopClocks();
8139             return;
8140         }
8141
8142         /* This method is only useful on engines that support ping */
8143         if (cps->lastPing != cps->lastPong) {
8144           if (gameMode == BeginningOfGame) {
8145             /* Extra move from before last new; ignore */
8146             if (appData.debugMode) {
8147                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8148             }
8149           } else {
8150             if (appData.debugMode) {
8151                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8152                         cps->which, gameMode);
8153             }
8154
8155             SendToProgram("undo\n", cps);
8156           }
8157           return;
8158         }
8159
8160         switch (gameMode) {
8161           case BeginningOfGame:
8162             /* Extra move from before last reset; ignore */
8163             if (appData.debugMode) {
8164                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8165             }
8166             return;
8167
8168           case EndOfGame:
8169           case IcsIdle:
8170           default:
8171             /* Extra move after we tried to stop.  The mode test is
8172                not a reliable way of detecting this problem, but it's
8173                the best we can do on engines that don't support ping.
8174             */
8175             if (appData.debugMode) {
8176                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8177                         cps->which, gameMode);
8178             }
8179             SendToProgram("undo\n", cps);
8180             return;
8181
8182           case MachinePlaysWhite:
8183           case IcsPlayingWhite:
8184             machineWhite = TRUE;
8185             break;
8186
8187           case MachinePlaysBlack:
8188           case IcsPlayingBlack:
8189             machineWhite = FALSE;
8190             break;
8191
8192           case TwoMachinesPlay:
8193             machineWhite = (cps->twoMachinesColor[0] == 'w');
8194             break;
8195         }
8196         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8197             if (appData.debugMode) {
8198                 fprintf(debugFP,
8199                         "Ignoring move out of turn by %s, gameMode %d"
8200                         ", forwardMost %d\n",
8201                         cps->which, gameMode, forwardMostMove);
8202             }
8203             return;
8204         }
8205
8206         if(cps->alphaRank) AlphaRank(machineMove, 4);
8207         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8208                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8209             /* Machine move could not be parsed; ignore it. */
8210           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8211                     machineMove, _(cps->which));
8212             DisplayMoveError(buf1);
8213             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8214                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8215             if (gameMode == TwoMachinesPlay) {
8216               GameEnds(machineWhite ? BlackWins : WhiteWins,
8217                        buf1, GE_XBOARD);
8218             }
8219             return;
8220         }
8221
8222         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8223         /* So we have to redo legality test with true e.p. status here,  */
8224         /* to make sure an illegal e.p. capture does not slip through,   */
8225         /* to cause a forfeit on a justified illegal-move complaint      */
8226         /* of the opponent.                                              */
8227         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8228            ChessMove moveType;
8229            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8230                              fromY, fromX, toY, toX, promoChar);
8231             if(moveType == IllegalMove) {
8232               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8233                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8234                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8235                            buf1, GE_XBOARD);
8236                 return;
8237            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8238            /* [HGM] Kludge to handle engines that send FRC-style castling
8239               when they shouldn't (like TSCP-Gothic) */
8240            switch(moveType) {
8241              case WhiteASideCastleFR:
8242              case BlackASideCastleFR:
8243                toX+=2;
8244                currentMoveString[2]++;
8245                break;
8246              case WhiteHSideCastleFR:
8247              case BlackHSideCastleFR:
8248                toX--;
8249                currentMoveString[2]--;
8250                break;
8251              default: ; // nothing to do, but suppresses warning of pedantic compilers
8252            }
8253         }
8254         hintRequested = FALSE;
8255         lastHint[0] = NULLCHAR;
8256         bookRequested = FALSE;
8257         /* Program may be pondering now */
8258         cps->maybeThinking = TRUE;
8259         if (cps->sendTime == 2) cps->sendTime = 1;
8260         if (cps->offeredDraw) cps->offeredDraw--;
8261
8262         /* [AS] Save move info*/
8263         pvInfoList[ forwardMostMove ].score = programStats.score;
8264         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8265         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8266
8267         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8268
8269         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8270         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8271             int count = 0;
8272
8273             while( count < adjudicateLossPlies ) {
8274                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8275
8276                 if( count & 1 ) {
8277                     score = -score; /* Flip score for winning side */
8278                 }
8279
8280                 if( score > adjudicateLossThreshold ) {
8281                     break;
8282                 }
8283
8284                 count++;
8285             }
8286
8287             if( count >= adjudicateLossPlies ) {
8288                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8289
8290                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8291                     "Xboard adjudication",
8292                     GE_XBOARD );
8293
8294                 return;
8295             }
8296         }
8297
8298         if(Adjudicate(cps)) {
8299             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8300             return; // [HGM] adjudicate: for all automatic game ends
8301         }
8302
8303 #if ZIPPY
8304         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8305             first.initDone) {
8306           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8307                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8308                 SendToICS("draw ");
8309                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8310           }
8311           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8312           ics_user_moved = 1;
8313           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8314                 char buf[3*MSG_SIZ];
8315
8316                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8317                         programStats.score / 100.,
8318                         programStats.depth,
8319                         programStats.time / 100.,
8320                         (unsigned int)programStats.nodes,
8321                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8322                         programStats.movelist);
8323                 SendToICS(buf);
8324 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8325           }
8326         }
8327 #endif
8328
8329         /* [AS] Clear stats for next move */
8330         ClearProgramStats();
8331         thinkOutput[0] = NULLCHAR;
8332         hiddenThinkOutputState = 0;
8333
8334         bookHit = NULL;
8335         if (gameMode == TwoMachinesPlay) {
8336             /* [HGM] relaying draw offers moved to after reception of move */
8337             /* and interpreting offer as claim if it brings draw condition */
8338             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8339                 SendToProgram("draw\n", cps->other);
8340             }
8341             if (cps->other->sendTime) {
8342                 SendTimeRemaining(cps->other,
8343                                   cps->other->twoMachinesColor[0] == 'w');
8344             }
8345             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8346             if (firstMove && !bookHit) {
8347                 firstMove = FALSE;
8348                 if (cps->other->useColors) {
8349                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8350                 }
8351                 SendToProgram("go\n", cps->other);
8352             }
8353             cps->other->maybeThinking = TRUE;
8354         }
8355
8356         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8357
8358         if (!pausing && appData.ringBellAfterMoves) {
8359             RingBell();
8360         }
8361
8362         /*
8363          * Reenable menu items that were disabled while
8364          * machine was thinking
8365          */
8366         if (gameMode != TwoMachinesPlay)
8367             SetUserThinkingEnables();
8368
8369         // [HGM] book: after book hit opponent has received move and is now in force mode
8370         // force the book reply into it, and then fake that it outputted this move by jumping
8371         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8372         if(bookHit) {
8373                 static char bookMove[MSG_SIZ]; // a bit generous?
8374
8375                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8376                 strcat(bookMove, bookHit);
8377                 message = bookMove;
8378                 cps = cps->other;
8379                 programStats.nodes = programStats.depth = programStats.time =
8380                 programStats.score = programStats.got_only_move = 0;
8381                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8382
8383                 if(cps->lastPing != cps->lastPong) {
8384                     savedMessage = message; // args for deferred call
8385                     savedState = cps;
8386                     ScheduleDelayedEvent(DeferredBookMove, 10);
8387                     return;
8388                 }
8389                 goto FakeBookMove;
8390         }
8391
8392         return;
8393     }
8394
8395     /* Set special modes for chess engines.  Later something general
8396      *  could be added here; for now there is just one kludge feature,
8397      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8398      *  when "xboard" is given as an interactive command.
8399      */
8400     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8401         cps->useSigint = FALSE;
8402         cps->useSigterm = FALSE;
8403     }
8404     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8405       ParseFeatures(message+8, cps);
8406       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8407     }
8408
8409     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8410                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8411       int dummy, s=6; char buf[MSG_SIZ];
8412       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8413       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8414       if(startedFromSetupPosition) return;
8415       ParseFEN(boards[0], &dummy, message+s);
8416       DrawPosition(TRUE, boards[0]);
8417       startedFromSetupPosition = TRUE;
8418       return;
8419     }
8420     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8421      * want this, I was asked to put it in, and obliged.
8422      */
8423     if (!strncmp(message, "setboard ", 9)) {
8424         Board initial_position;
8425
8426         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8427
8428         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8429             DisplayError(_("Bad FEN received from engine"), 0);
8430             return ;
8431         } else {
8432            Reset(TRUE, FALSE);
8433            CopyBoard(boards[0], initial_position);
8434            initialRulePlies = FENrulePlies;
8435            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8436            else gameMode = MachinePlaysBlack;
8437            DrawPosition(FALSE, boards[currentMove]);
8438         }
8439         return;
8440     }
8441
8442     /*
8443      * Look for communication commands
8444      */
8445     if (!strncmp(message, "telluser ", 9)) {
8446         if(message[9] == '\\' && message[10] == '\\')
8447             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8448         PlayTellSound();
8449         DisplayNote(message + 9);
8450         return;
8451     }
8452     if (!strncmp(message, "tellusererror ", 14)) {
8453         cps->userError = 1;
8454         if(message[14] == '\\' && message[15] == '\\')
8455             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8456         PlayTellSound();
8457         DisplayError(message + 14, 0);
8458         return;
8459     }
8460     if (!strncmp(message, "tellopponent ", 13)) {
8461       if (appData.icsActive) {
8462         if (loggedOn) {
8463           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8464           SendToICS(buf1);
8465         }
8466       } else {
8467         DisplayNote(message + 13);
8468       }
8469       return;
8470     }
8471     if (!strncmp(message, "tellothers ", 11)) {
8472       if (appData.icsActive) {
8473         if (loggedOn) {
8474           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8475           SendToICS(buf1);
8476         }
8477       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8478       return;
8479     }
8480     if (!strncmp(message, "tellall ", 8)) {
8481       if (appData.icsActive) {
8482         if (loggedOn) {
8483           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8484           SendToICS(buf1);
8485         }
8486       } else {
8487         DisplayNote(message + 8);
8488       }
8489       return;
8490     }
8491     if (strncmp(message, "warning", 7) == 0) {
8492         /* Undocumented feature, use tellusererror in new code */
8493         DisplayError(message, 0);
8494         return;
8495     }
8496     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8497         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8498         strcat(realname, " query");
8499         AskQuestion(realname, buf2, buf1, cps->pr);
8500         return;
8501     }
8502     /* Commands from the engine directly to ICS.  We don't allow these to be
8503      *  sent until we are logged on. Crafty kibitzes have been known to
8504      *  interfere with the login process.
8505      */
8506     if (loggedOn) {
8507         if (!strncmp(message, "tellics ", 8)) {
8508             SendToICS(message + 8);
8509             SendToICS("\n");
8510             return;
8511         }
8512         if (!strncmp(message, "tellicsnoalias ", 15)) {
8513             SendToICS(ics_prefix);
8514             SendToICS(message + 15);
8515             SendToICS("\n");
8516             return;
8517         }
8518         /* The following are for backward compatibility only */
8519         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8520             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8521             SendToICS(ics_prefix);
8522             SendToICS(message);
8523             SendToICS("\n");
8524             return;
8525         }
8526     }
8527     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8528         return;
8529     }
8530     /*
8531      * If the move is illegal, cancel it and redraw the board.
8532      * Also deal with other error cases.  Matching is rather loose
8533      * here to accommodate engines written before the spec.
8534      */
8535     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8536         strncmp(message, "Error", 5) == 0) {
8537         if (StrStr(message, "name") ||
8538             StrStr(message, "rating") || StrStr(message, "?") ||
8539             StrStr(message, "result") || StrStr(message, "board") ||
8540             StrStr(message, "bk") || StrStr(message, "computer") ||
8541             StrStr(message, "variant") || StrStr(message, "hint") ||
8542             StrStr(message, "random") || StrStr(message, "depth") ||
8543             StrStr(message, "accepted")) {
8544             return;
8545         }
8546         if (StrStr(message, "protover")) {
8547           /* Program is responding to input, so it's apparently done
8548              initializing, and this error message indicates it is
8549              protocol version 1.  So we don't need to wait any longer
8550              for it to initialize and send feature commands. */
8551           FeatureDone(cps, 1);
8552           cps->protocolVersion = 1;
8553           return;
8554         }
8555         cps->maybeThinking = FALSE;
8556
8557         if (StrStr(message, "draw")) {
8558             /* Program doesn't have "draw" command */
8559             cps->sendDrawOffers = 0;
8560             return;
8561         }
8562         if (cps->sendTime != 1 &&
8563             (StrStr(message, "time") || StrStr(message, "otim"))) {
8564           /* Program apparently doesn't have "time" or "otim" command */
8565           cps->sendTime = 0;
8566           return;
8567         }
8568         if (StrStr(message, "analyze")) {
8569             cps->analysisSupport = FALSE;
8570             cps->analyzing = FALSE;
8571 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8572             EditGameEvent(); // [HGM] try to preserve loaded game
8573             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8574             DisplayError(buf2, 0);
8575             return;
8576         }
8577         if (StrStr(message, "(no matching move)st")) {
8578           /* Special kludge for GNU Chess 4 only */
8579           cps->stKludge = TRUE;
8580           SendTimeControl(cps, movesPerSession, timeControl,
8581                           timeIncrement, appData.searchDepth,
8582                           searchTime);
8583           return;
8584         }
8585         if (StrStr(message, "(no matching move)sd")) {
8586           /* Special kludge for GNU Chess 4 only */
8587           cps->sdKludge = TRUE;
8588           SendTimeControl(cps, movesPerSession, timeControl,
8589                           timeIncrement, appData.searchDepth,
8590                           searchTime);
8591           return;
8592         }
8593         if (!StrStr(message, "llegal")) {
8594             return;
8595         }
8596         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8597             gameMode == IcsIdle) return;
8598         if (forwardMostMove <= backwardMostMove) return;
8599         if (pausing) PauseEvent();
8600       if(appData.forceIllegal) {
8601             // [HGM] illegal: machine refused move; force position after move into it
8602           SendToProgram("force\n", cps);
8603           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8604                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8605                 // when black is to move, while there might be nothing on a2 or black
8606                 // might already have the move. So send the board as if white has the move.
8607                 // But first we must change the stm of the engine, as it refused the last move
8608                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8609                 if(WhiteOnMove(forwardMostMove)) {
8610                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8611                     SendBoard(cps, forwardMostMove); // kludgeless board
8612                 } else {
8613                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8614                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8615                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8616                 }
8617           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8618             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8619                  gameMode == TwoMachinesPlay)
8620               SendToProgram("go\n", cps);
8621             return;
8622       } else
8623         if (gameMode == PlayFromGameFile) {
8624             /* Stop reading this game file */
8625             gameMode = EditGame;
8626             ModeHighlight();
8627         }
8628         /* [HGM] illegal-move claim should forfeit game when Xboard */
8629         /* only passes fully legal moves                            */
8630         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8631             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8632                                 "False illegal-move claim", GE_XBOARD );
8633             return; // do not take back move we tested as valid
8634         }
8635         currentMove = forwardMostMove-1;
8636         DisplayMove(currentMove-1); /* before DisplayMoveError */
8637         SwitchClocks(forwardMostMove-1); // [HGM] race
8638         DisplayBothClocks();
8639         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8640                 parseList[currentMove], _(cps->which));
8641         DisplayMoveError(buf1);
8642         DrawPosition(FALSE, boards[currentMove]);
8643
8644         SetUserThinkingEnables();
8645         return;
8646     }
8647     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8648         /* Program has a broken "time" command that
8649            outputs a string not ending in newline.
8650            Don't use it. */
8651         cps->sendTime = 0;
8652     }
8653
8654     /*
8655      * If chess program startup fails, exit with an error message.
8656      * Attempts to recover here are futile. [HGM] Well, we try anyway
8657      */
8658     if ((StrStr(message, "unknown host") != NULL)
8659         || (StrStr(message, "No remote directory") != NULL)
8660         || (StrStr(message, "not found") != NULL)
8661         || (StrStr(message, "No such file") != NULL)
8662         || (StrStr(message, "can't alloc") != NULL)
8663         || (StrStr(message, "Permission denied") != NULL)) {
8664
8665         cps->maybeThinking = FALSE;
8666         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8667                 _(cps->which), cps->program, cps->host, message);
8668         RemoveInputSource(cps->isr);
8669         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8670             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8671             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8672         }
8673         return;
8674     }
8675
8676     /*
8677      * Look for hint output
8678      */
8679     if (sscanf(message, "Hint: %s", buf1) == 1) {
8680         if (cps == &first && hintRequested) {
8681             hintRequested = FALSE;
8682             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8683                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8684                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8685                                     PosFlags(forwardMostMove),
8686                                     fromY, fromX, toY, toX, promoChar, buf1);
8687                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8688                 DisplayInformation(buf2);
8689             } else {
8690                 /* Hint move could not be parsed!? */
8691               snprintf(buf2, sizeof(buf2),
8692                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8693                         buf1, _(cps->which));
8694                 DisplayError(buf2, 0);
8695             }
8696         } else {
8697           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8698         }
8699         return;
8700     }
8701
8702     /*
8703      * Ignore other messages if game is not in progress
8704      */
8705     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8706         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8707
8708     /*
8709      * look for win, lose, draw, or draw offer
8710      */
8711     if (strncmp(message, "1-0", 3) == 0) {
8712         char *p, *q, *r = "";
8713         p = strchr(message, '{');
8714         if (p) {
8715             q = strchr(p, '}');
8716             if (q) {
8717                 *q = NULLCHAR;
8718                 r = p + 1;
8719             }
8720         }
8721         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8722         return;
8723     } else if (strncmp(message, "0-1", 3) == 0) {
8724         char *p, *q, *r = "";
8725         p = strchr(message, '{');
8726         if (p) {
8727             q = strchr(p, '}');
8728             if (q) {
8729                 *q = NULLCHAR;
8730                 r = p + 1;
8731             }
8732         }
8733         /* Kludge for Arasan 4.1 bug */
8734         if (strcmp(r, "Black resigns") == 0) {
8735             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8736             return;
8737         }
8738         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8739         return;
8740     } else if (strncmp(message, "1/2", 3) == 0) {
8741         char *p, *q, *r = "";
8742         p = strchr(message, '{');
8743         if (p) {
8744             q = strchr(p, '}');
8745             if (q) {
8746                 *q = NULLCHAR;
8747                 r = p + 1;
8748             }
8749         }
8750
8751         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8752         return;
8753
8754     } else if (strncmp(message, "White resign", 12) == 0) {
8755         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8756         return;
8757     } else if (strncmp(message, "Black resign", 12) == 0) {
8758         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8759         return;
8760     } else if (strncmp(message, "White matches", 13) == 0 ||
8761                strncmp(message, "Black matches", 13) == 0   ) {
8762         /* [HGM] ignore GNUShogi noises */
8763         return;
8764     } else if (strncmp(message, "White", 5) == 0 &&
8765                message[5] != '(' &&
8766                StrStr(message, "Black") == NULL) {
8767         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8768         return;
8769     } else if (strncmp(message, "Black", 5) == 0 &&
8770                message[5] != '(') {
8771         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8772         return;
8773     } else if (strcmp(message, "resign") == 0 ||
8774                strcmp(message, "computer resigns") == 0) {
8775         switch (gameMode) {
8776           case MachinePlaysBlack:
8777           case IcsPlayingBlack:
8778             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8779             break;
8780           case MachinePlaysWhite:
8781           case IcsPlayingWhite:
8782             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8783             break;
8784           case TwoMachinesPlay:
8785             if (cps->twoMachinesColor[0] == 'w')
8786               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8787             else
8788               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8789             break;
8790           default:
8791             /* can't happen */
8792             break;
8793         }
8794         return;
8795     } else if (strncmp(message, "opponent mates", 14) == 0) {
8796         switch (gameMode) {
8797           case MachinePlaysBlack:
8798           case IcsPlayingBlack:
8799             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8800             break;
8801           case MachinePlaysWhite:
8802           case IcsPlayingWhite:
8803             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8804             break;
8805           case TwoMachinesPlay:
8806             if (cps->twoMachinesColor[0] == 'w')
8807               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8808             else
8809               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8810             break;
8811           default:
8812             /* can't happen */
8813             break;
8814         }
8815         return;
8816     } else if (strncmp(message, "computer mates", 14) == 0) {
8817         switch (gameMode) {
8818           case MachinePlaysBlack:
8819           case IcsPlayingBlack:
8820             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8821             break;
8822           case MachinePlaysWhite:
8823           case IcsPlayingWhite:
8824             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8825             break;
8826           case TwoMachinesPlay:
8827             if (cps->twoMachinesColor[0] == 'w')
8828               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8829             else
8830               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8831             break;
8832           default:
8833             /* can't happen */
8834             break;
8835         }
8836         return;
8837     } else if (strncmp(message, "checkmate", 9) == 0) {
8838         if (WhiteOnMove(forwardMostMove)) {
8839             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8840         } else {
8841             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8842         }
8843         return;
8844     } else if (strstr(message, "Draw") != NULL ||
8845                strstr(message, "game is a draw") != NULL) {
8846         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8847         return;
8848     } else if (strstr(message, "offer") != NULL &&
8849                strstr(message, "draw") != NULL) {
8850 #if ZIPPY
8851         if (appData.zippyPlay && first.initDone) {
8852             /* Relay offer to ICS */
8853             SendToICS(ics_prefix);
8854             SendToICS("draw\n");
8855         }
8856 #endif
8857         cps->offeredDraw = 2; /* valid until this engine moves twice */
8858         if (gameMode == TwoMachinesPlay) {
8859             if (cps->other->offeredDraw) {
8860                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8861             /* [HGM] in two-machine mode we delay relaying draw offer      */
8862             /* until after we also have move, to see if it is really claim */
8863             }
8864         } else if (gameMode == MachinePlaysWhite ||
8865                    gameMode == MachinePlaysBlack) {
8866           if (userOfferedDraw) {
8867             DisplayInformation(_("Machine accepts your draw offer"));
8868             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8869           } else {
8870             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8871           }
8872         }
8873     }
8874
8875
8876     /*
8877      * Look for thinking output
8878      */
8879     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8880           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8881                                 ) {
8882         int plylev, mvleft, mvtot, curscore, time;
8883         char mvname[MOVE_LEN];
8884         u64 nodes; // [DM]
8885         char plyext;
8886         int ignore = FALSE;
8887         int prefixHint = FALSE;
8888         mvname[0] = NULLCHAR;
8889
8890         switch (gameMode) {
8891           case MachinePlaysBlack:
8892           case IcsPlayingBlack:
8893             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8894             break;
8895           case MachinePlaysWhite:
8896           case IcsPlayingWhite:
8897             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8898             break;
8899           case AnalyzeMode:
8900           case AnalyzeFile:
8901             break;
8902           case IcsObserving: /* [DM] icsEngineAnalyze */
8903             if (!appData.icsEngineAnalyze) ignore = TRUE;
8904             break;
8905           case TwoMachinesPlay:
8906             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8907                 ignore = TRUE;
8908             }
8909             break;
8910           default:
8911             ignore = TRUE;
8912             break;
8913         }
8914
8915         if (!ignore) {
8916             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8917             buf1[0] = NULLCHAR;
8918             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8919                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8920
8921                 if (plyext != ' ' && plyext != '\t') {
8922                     time *= 100;
8923                 }
8924
8925                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8926                 if( cps->scoreIsAbsolute &&
8927                     ( gameMode == MachinePlaysBlack ||
8928                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8929                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8930                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8931                      !WhiteOnMove(currentMove)
8932                     ) )
8933                 {
8934                     curscore = -curscore;
8935                 }
8936
8937                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8938
8939                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8940                         char buf[MSG_SIZ];
8941                         FILE *f;
8942                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8943                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8944                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8945                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8946                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8947                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8948                                 fclose(f);
8949                         } else DisplayError(_("failed writing PV"), 0);
8950                 }
8951
8952                 tempStats.depth = plylev;
8953                 tempStats.nodes = nodes;
8954                 tempStats.time = time;
8955                 tempStats.score = curscore;
8956                 tempStats.got_only_move = 0;
8957
8958                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8959                         int ticklen;
8960
8961                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8962                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8963                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8964                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8965                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8966                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8967                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8968                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8969                 }
8970
8971                 /* Buffer overflow protection */
8972                 if (pv[0] != NULLCHAR) {
8973                     if (strlen(pv) >= sizeof(tempStats.movelist)
8974                         && appData.debugMode) {
8975                         fprintf(debugFP,
8976                                 "PV is too long; using the first %u bytes.\n",
8977                                 (unsigned) sizeof(tempStats.movelist) - 1);
8978                     }
8979
8980                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8981                 } else {
8982                     sprintf(tempStats.movelist, " no PV\n");
8983                 }
8984
8985                 if (tempStats.seen_stat) {
8986                     tempStats.ok_to_send = 1;
8987                 }
8988
8989                 if (strchr(tempStats.movelist, '(') != NULL) {
8990                     tempStats.line_is_book = 1;
8991                     tempStats.nr_moves = 0;
8992                     tempStats.moves_left = 0;
8993                 } else {
8994                     tempStats.line_is_book = 0;
8995                 }
8996
8997                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8998                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8999
9000                 SendProgramStatsToFrontend( cps, &tempStats );
9001
9002                 /*
9003                     [AS] Protect the thinkOutput buffer from overflow... this
9004                     is only useful if buf1 hasn't overflowed first!
9005                 */
9006                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9007                          plylev,
9008                          (gameMode == TwoMachinesPlay ?
9009                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9010                          ((double) curscore) / 100.0,
9011                          prefixHint ? lastHint : "",
9012                          prefixHint ? " " : "" );
9013
9014                 if( buf1[0] != NULLCHAR ) {
9015                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9016
9017                     if( strlen(pv) > max_len ) {
9018                         if( appData.debugMode) {
9019                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9020                         }
9021                         pv[max_len+1] = '\0';
9022                     }
9023
9024                     strcat( thinkOutput, pv);
9025                 }
9026
9027                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9028                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9029                     DisplayMove(currentMove - 1);
9030                 }
9031                 return;
9032
9033             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9034                 /* crafty (9.25+) says "(only move) <move>"
9035                  * if there is only 1 legal move
9036                  */
9037                 sscanf(p, "(only move) %s", buf1);
9038                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9039                 sprintf(programStats.movelist, "%s (only move)", buf1);
9040                 programStats.depth = 1;
9041                 programStats.nr_moves = 1;
9042                 programStats.moves_left = 1;
9043                 programStats.nodes = 1;
9044                 programStats.time = 1;
9045                 programStats.got_only_move = 1;
9046
9047                 /* Not really, but we also use this member to
9048                    mean "line isn't going to change" (Crafty
9049                    isn't searching, so stats won't change) */
9050                 programStats.line_is_book = 1;
9051
9052                 SendProgramStatsToFrontend( cps, &programStats );
9053
9054                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9055                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9056                     DisplayMove(currentMove - 1);
9057                 }
9058                 return;
9059             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9060                               &time, &nodes, &plylev, &mvleft,
9061                               &mvtot, mvname) >= 5) {
9062                 /* The stat01: line is from Crafty (9.29+) in response
9063                    to the "." command */
9064                 programStats.seen_stat = 1;
9065                 cps->maybeThinking = TRUE;
9066
9067                 if (programStats.got_only_move || !appData.periodicUpdates)
9068                   return;
9069
9070                 programStats.depth = plylev;
9071                 programStats.time = time;
9072                 programStats.nodes = nodes;
9073                 programStats.moves_left = mvleft;
9074                 programStats.nr_moves = mvtot;
9075                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9076                 programStats.ok_to_send = 1;
9077                 programStats.movelist[0] = '\0';
9078
9079                 SendProgramStatsToFrontend( cps, &programStats );
9080
9081                 return;
9082
9083             } else if (strncmp(message,"++",2) == 0) {
9084                 /* Crafty 9.29+ outputs this */
9085                 programStats.got_fail = 2;
9086                 return;
9087
9088             } else if (strncmp(message,"--",2) == 0) {
9089                 /* Crafty 9.29+ outputs this */
9090                 programStats.got_fail = 1;
9091                 return;
9092
9093             } else if (thinkOutput[0] != NULLCHAR &&
9094                        strncmp(message, "    ", 4) == 0) {
9095                 unsigned message_len;
9096
9097                 p = message;
9098                 while (*p && *p == ' ') p++;
9099
9100                 message_len = strlen( p );
9101
9102                 /* [AS] Avoid buffer overflow */
9103                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9104                     strcat(thinkOutput, " ");
9105                     strcat(thinkOutput, p);
9106                 }
9107
9108                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9109                     strcat(programStats.movelist, " ");
9110                     strcat(programStats.movelist, p);
9111                 }
9112
9113                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9114                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9115                     DisplayMove(currentMove - 1);
9116                 }
9117                 return;
9118             }
9119         }
9120         else {
9121             buf1[0] = NULLCHAR;
9122
9123             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9124                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9125             {
9126                 ChessProgramStats cpstats;
9127
9128                 if (plyext != ' ' && plyext != '\t') {
9129                     time *= 100;
9130                 }
9131
9132                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9133                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9134                     curscore = -curscore;
9135                 }
9136
9137                 cpstats.depth = plylev;
9138                 cpstats.nodes = nodes;
9139                 cpstats.time = time;
9140                 cpstats.score = curscore;
9141                 cpstats.got_only_move = 0;
9142                 cpstats.movelist[0] = '\0';
9143
9144                 if (buf1[0] != NULLCHAR) {
9145                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9146                 }
9147
9148                 cpstats.ok_to_send = 0;
9149                 cpstats.line_is_book = 0;
9150                 cpstats.nr_moves = 0;
9151                 cpstats.moves_left = 0;
9152
9153                 SendProgramStatsToFrontend( cps, &cpstats );
9154             }
9155         }
9156     }
9157 }
9158
9159
9160 /* Parse a game score from the character string "game", and
9161    record it as the history of the current game.  The game
9162    score is NOT assumed to start from the standard position.
9163    The display is not updated in any way.
9164    */
9165 void
9166 ParseGameHistory (char *game)
9167 {
9168     ChessMove moveType;
9169     int fromX, fromY, toX, toY, boardIndex;
9170     char promoChar;
9171     char *p, *q;
9172     char buf[MSG_SIZ];
9173
9174     if (appData.debugMode)
9175       fprintf(debugFP, "Parsing game history: %s\n", game);
9176
9177     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9178     gameInfo.site = StrSave(appData.icsHost);
9179     gameInfo.date = PGNDate();
9180     gameInfo.round = StrSave("-");
9181
9182     /* Parse out names of players */
9183     while (*game == ' ') game++;
9184     p = buf;
9185     while (*game != ' ') *p++ = *game++;
9186     *p = NULLCHAR;
9187     gameInfo.white = StrSave(buf);
9188     while (*game == ' ') game++;
9189     p = buf;
9190     while (*game != ' ' && *game != '\n') *p++ = *game++;
9191     *p = NULLCHAR;
9192     gameInfo.black = StrSave(buf);
9193
9194     /* Parse moves */
9195     boardIndex = blackPlaysFirst ? 1 : 0;
9196     yynewstr(game);
9197     for (;;) {
9198         yyboardindex = boardIndex;
9199         moveType = (ChessMove) Myylex();
9200         switch (moveType) {
9201           case IllegalMove:             /* maybe suicide chess, etc. */
9202   if (appData.debugMode) {
9203     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9204     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9205     setbuf(debugFP, NULL);
9206   }
9207           case WhitePromotion:
9208           case BlackPromotion:
9209           case WhiteNonPromotion:
9210           case BlackNonPromotion:
9211           case NormalMove:
9212           case WhiteCapturesEnPassant:
9213           case BlackCapturesEnPassant:
9214           case WhiteKingSideCastle:
9215           case WhiteQueenSideCastle:
9216           case BlackKingSideCastle:
9217           case BlackQueenSideCastle:
9218           case WhiteKingSideCastleWild:
9219           case WhiteQueenSideCastleWild:
9220           case BlackKingSideCastleWild:
9221           case BlackQueenSideCastleWild:
9222           /* PUSH Fabien */
9223           case WhiteHSideCastleFR:
9224           case WhiteASideCastleFR:
9225           case BlackHSideCastleFR:
9226           case BlackASideCastleFR:
9227           /* POP Fabien */
9228             fromX = currentMoveString[0] - AAA;
9229             fromY = currentMoveString[1] - ONE;
9230             toX = currentMoveString[2] - AAA;
9231             toY = currentMoveString[3] - ONE;
9232             promoChar = currentMoveString[4];
9233             break;
9234           case WhiteDrop:
9235           case BlackDrop:
9236             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9237             fromX = moveType == WhiteDrop ?
9238               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9239             (int) CharToPiece(ToLower(currentMoveString[0]));
9240             fromY = DROP_RANK;
9241             toX = currentMoveString[2] - AAA;
9242             toY = currentMoveString[3] - ONE;
9243             promoChar = NULLCHAR;
9244             break;
9245           case AmbiguousMove:
9246             /* bug? */
9247             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9248   if (appData.debugMode) {
9249     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9250     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9251     setbuf(debugFP, NULL);
9252   }
9253             DisplayError(buf, 0);
9254             return;
9255           case ImpossibleMove:
9256             /* bug? */
9257             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9258   if (appData.debugMode) {
9259     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9260     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9261     setbuf(debugFP, NULL);
9262   }
9263             DisplayError(buf, 0);
9264             return;
9265           case EndOfFile:
9266             if (boardIndex < backwardMostMove) {
9267                 /* Oops, gap.  How did that happen? */
9268                 DisplayError(_("Gap in move list"), 0);
9269                 return;
9270             }
9271             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9272             if (boardIndex > forwardMostMove) {
9273                 forwardMostMove = boardIndex;
9274             }
9275             return;
9276           case ElapsedTime:
9277             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9278                 strcat(parseList[boardIndex-1], " ");
9279                 strcat(parseList[boardIndex-1], yy_text);
9280             }
9281             continue;
9282           case Comment:
9283           case PGNTag:
9284           case NAG:
9285           default:
9286             /* ignore */
9287             continue;
9288           case WhiteWins:
9289           case BlackWins:
9290           case GameIsDrawn:
9291           case GameUnfinished:
9292             if (gameMode == IcsExamining) {
9293                 if (boardIndex < backwardMostMove) {
9294                     /* Oops, gap.  How did that happen? */
9295                     return;
9296                 }
9297                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9298                 return;
9299             }
9300             gameInfo.result = moveType;
9301             p = strchr(yy_text, '{');
9302             if (p == NULL) p = strchr(yy_text, '(');
9303             if (p == NULL) {
9304                 p = yy_text;
9305                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9306             } else {
9307                 q = strchr(p, *p == '{' ? '}' : ')');
9308                 if (q != NULL) *q = NULLCHAR;
9309                 p++;
9310             }
9311             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9312             gameInfo.resultDetails = StrSave(p);
9313             continue;
9314         }
9315         if (boardIndex >= forwardMostMove &&
9316             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9317             backwardMostMove = blackPlaysFirst ? 1 : 0;
9318             return;
9319         }
9320         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9321                                  fromY, fromX, toY, toX, promoChar,
9322                                  parseList[boardIndex]);
9323         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9324         /* currentMoveString is set as a side-effect of yylex */
9325         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9326         strcat(moveList[boardIndex], "\n");
9327         boardIndex++;
9328         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9329         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9330           case MT_NONE:
9331           case MT_STALEMATE:
9332           default:
9333             break;
9334           case MT_CHECK:
9335             if(gameInfo.variant != VariantShogi)
9336                 strcat(parseList[boardIndex - 1], "+");
9337             break;
9338           case MT_CHECKMATE:
9339           case MT_STAINMATE:
9340             strcat(parseList[boardIndex - 1], "#");
9341             break;
9342         }
9343     }
9344 }
9345
9346
9347 /* Apply a move to the given board  */
9348 void
9349 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9350 {
9351   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9352   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9353
9354     /* [HGM] compute & store e.p. status and castling rights for new position */
9355     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9356
9357       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9358       oldEP = (signed char)board[EP_STATUS];
9359       board[EP_STATUS] = EP_NONE;
9360
9361   if (fromY == DROP_RANK) {
9362         /* must be first */
9363         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9364             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9365             return;
9366         }
9367         piece = board[toY][toX] = (ChessSquare) fromX;
9368   } else {
9369       int i;
9370
9371       if( board[toY][toX] != EmptySquare )
9372            board[EP_STATUS] = EP_CAPTURE;
9373
9374       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9375            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9376                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9377       } else
9378       if( board[fromY][fromX] == WhitePawn ) {
9379            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9380                board[EP_STATUS] = EP_PAWN_MOVE;
9381            if( toY-fromY==2) {
9382                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9383                         gameInfo.variant != VariantBerolina || toX < fromX)
9384                       board[EP_STATUS] = toX | berolina;
9385                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9386                         gameInfo.variant != VariantBerolina || toX > fromX)
9387                       board[EP_STATUS] = toX;
9388            }
9389       } else
9390       if( board[fromY][fromX] == BlackPawn ) {
9391            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9392                board[EP_STATUS] = EP_PAWN_MOVE;
9393            if( toY-fromY== -2) {
9394                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9395                         gameInfo.variant != VariantBerolina || toX < fromX)
9396                       board[EP_STATUS] = toX | berolina;
9397                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9398                         gameInfo.variant != VariantBerolina || toX > fromX)
9399                       board[EP_STATUS] = toX;
9400            }
9401        }
9402
9403        for(i=0; i<nrCastlingRights; i++) {
9404            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9405               board[CASTLING][i] == toX   && castlingRank[i] == toY
9406              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9407        }
9408
9409        if(gameInfo.variant == VariantSChess) { // update virginity
9410            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9411            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9412            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9413            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9414        }
9415
9416      if (fromX == toX && fromY == toY) return;
9417
9418      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9419      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9420      if(gameInfo.variant == VariantKnightmate)
9421          king += (int) WhiteUnicorn - (int) WhiteKing;
9422
9423     /* Code added by Tord: */
9424     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9425     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9426         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9427       board[fromY][fromX] = EmptySquare;
9428       board[toY][toX] = EmptySquare;
9429       if((toX > fromX) != (piece == WhiteRook)) {
9430         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9431       } else {
9432         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9433       }
9434     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9435                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9436       board[fromY][fromX] = EmptySquare;
9437       board[toY][toX] = EmptySquare;
9438       if((toX > fromX) != (piece == BlackRook)) {
9439         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9440       } else {
9441         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9442       }
9443     /* End of code added by Tord */
9444
9445     } else if (board[fromY][fromX] == king
9446         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9447         && toY == fromY && toX > fromX+1) {
9448         board[fromY][fromX] = EmptySquare;
9449         board[toY][toX] = king;
9450         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9451         board[fromY][BOARD_RGHT-1] = EmptySquare;
9452     } else if (board[fromY][fromX] == king
9453         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9454                && toY == fromY && toX < fromX-1) {
9455         board[fromY][fromX] = EmptySquare;
9456         board[toY][toX] = king;
9457         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9458         board[fromY][BOARD_LEFT] = EmptySquare;
9459     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9460                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9461                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9462                ) {
9463         /* white pawn promotion */
9464         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9465         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9466             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9467         board[fromY][fromX] = EmptySquare;
9468     } else if ((fromY >= BOARD_HEIGHT>>1)
9469                && (toX != fromX)
9470                && gameInfo.variant != VariantXiangqi
9471                && gameInfo.variant != VariantBerolina
9472                && (board[fromY][fromX] == WhitePawn)
9473                && (board[toY][toX] == EmptySquare)) {
9474         board[fromY][fromX] = EmptySquare;
9475         board[toY][toX] = WhitePawn;
9476         captured = board[toY - 1][toX];
9477         board[toY - 1][toX] = EmptySquare;
9478     } else if ((fromY == BOARD_HEIGHT-4)
9479                && (toX == fromX)
9480                && gameInfo.variant == VariantBerolina
9481                && (board[fromY][fromX] == WhitePawn)
9482                && (board[toY][toX] == EmptySquare)) {
9483         board[fromY][fromX] = EmptySquare;
9484         board[toY][toX] = WhitePawn;
9485         if(oldEP & EP_BEROLIN_A) {
9486                 captured = board[fromY][fromX-1];
9487                 board[fromY][fromX-1] = EmptySquare;
9488         }else{  captured = board[fromY][fromX+1];
9489                 board[fromY][fromX+1] = EmptySquare;
9490         }
9491     } else if (board[fromY][fromX] == king
9492         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9493                && toY == fromY && toX > fromX+1) {
9494         board[fromY][fromX] = EmptySquare;
9495         board[toY][toX] = king;
9496         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9497         board[fromY][BOARD_RGHT-1] = EmptySquare;
9498     } else if (board[fromY][fromX] == king
9499         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9500                && toY == fromY && toX < fromX-1) {
9501         board[fromY][fromX] = EmptySquare;
9502         board[toY][toX] = king;
9503         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9504         board[fromY][BOARD_LEFT] = EmptySquare;
9505     } else if (fromY == 7 && fromX == 3
9506                && board[fromY][fromX] == BlackKing
9507                && toY == 7 && toX == 5) {
9508         board[fromY][fromX] = EmptySquare;
9509         board[toY][toX] = BlackKing;
9510         board[fromY][7] = EmptySquare;
9511         board[toY][4] = BlackRook;
9512     } else if (fromY == 7 && fromX == 3
9513                && board[fromY][fromX] == BlackKing
9514                && toY == 7 && toX == 1) {
9515         board[fromY][fromX] = EmptySquare;
9516         board[toY][toX] = BlackKing;
9517         board[fromY][0] = EmptySquare;
9518         board[toY][2] = BlackRook;
9519     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9520                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9521                && toY < promoRank && promoChar
9522                ) {
9523         /* black pawn promotion */
9524         board[toY][toX] = CharToPiece(ToLower(promoChar));
9525         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9526             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9527         board[fromY][fromX] = EmptySquare;
9528     } else if ((fromY < BOARD_HEIGHT>>1)
9529                && (toX != fromX)
9530                && gameInfo.variant != VariantXiangqi
9531                && gameInfo.variant != VariantBerolina
9532                && (board[fromY][fromX] == BlackPawn)
9533                && (board[toY][toX] == EmptySquare)) {
9534         board[fromY][fromX] = EmptySquare;
9535         board[toY][toX] = BlackPawn;
9536         captured = board[toY + 1][toX];
9537         board[toY + 1][toX] = EmptySquare;
9538     } else if ((fromY == 3)
9539                && (toX == fromX)
9540                && gameInfo.variant == VariantBerolina
9541                && (board[fromY][fromX] == BlackPawn)
9542                && (board[toY][toX] == EmptySquare)) {
9543         board[fromY][fromX] = EmptySquare;
9544         board[toY][toX] = BlackPawn;
9545         if(oldEP & EP_BEROLIN_A) {
9546                 captured = board[fromY][fromX-1];
9547                 board[fromY][fromX-1] = EmptySquare;
9548         }else{  captured = board[fromY][fromX+1];
9549                 board[fromY][fromX+1] = EmptySquare;
9550         }
9551     } else {
9552         board[toY][toX] = board[fromY][fromX];
9553         board[fromY][fromX] = EmptySquare;
9554     }
9555   }
9556
9557     if (gameInfo.holdingsWidth != 0) {
9558
9559       /* !!A lot more code needs to be written to support holdings  */
9560       /* [HGM] OK, so I have written it. Holdings are stored in the */
9561       /* penultimate board files, so they are automaticlly stored   */
9562       /* in the game history.                                       */
9563       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9564                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9565         /* Delete from holdings, by decreasing count */
9566         /* and erasing image if necessary            */
9567         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9568         if(p < (int) BlackPawn) { /* white drop */
9569              p -= (int)WhitePawn;
9570                  p = PieceToNumber((ChessSquare)p);
9571              if(p >= gameInfo.holdingsSize) p = 0;
9572              if(--board[p][BOARD_WIDTH-2] <= 0)
9573                   board[p][BOARD_WIDTH-1] = EmptySquare;
9574              if((int)board[p][BOARD_WIDTH-2] < 0)
9575                         board[p][BOARD_WIDTH-2] = 0;
9576         } else {                  /* black drop */
9577              p -= (int)BlackPawn;
9578                  p = PieceToNumber((ChessSquare)p);
9579              if(p >= gameInfo.holdingsSize) p = 0;
9580              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9581                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9582              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9583                         board[BOARD_HEIGHT-1-p][1] = 0;
9584         }
9585       }
9586       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9587           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9588         /* [HGM] holdings: Add to holdings, if holdings exist */
9589         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9590                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9591                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9592         }
9593         p = (int) captured;
9594         if (p >= (int) BlackPawn) {
9595           p -= (int)BlackPawn;
9596           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9597                   /* in Shogi restore piece to its original  first */
9598                   captured = (ChessSquare) (DEMOTED captured);
9599                   p = DEMOTED p;
9600           }
9601           p = PieceToNumber((ChessSquare)p);
9602           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9603           board[p][BOARD_WIDTH-2]++;
9604           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9605         } else {
9606           p -= (int)WhitePawn;
9607           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9608                   captured = (ChessSquare) (DEMOTED captured);
9609                   p = DEMOTED p;
9610           }
9611           p = PieceToNumber((ChessSquare)p);
9612           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9613           board[BOARD_HEIGHT-1-p][1]++;
9614           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9615         }
9616       }
9617     } else if (gameInfo.variant == VariantAtomic) {
9618       if (captured != EmptySquare) {
9619         int y, x;
9620         for (y = toY-1; y <= toY+1; y++) {
9621           for (x = toX-1; x <= toX+1; x++) {
9622             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9623                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9624               board[y][x] = EmptySquare;
9625             }
9626           }
9627         }
9628         board[toY][toX] = EmptySquare;
9629       }
9630     }
9631     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9632         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9633     } else
9634     if(promoChar == '+') {
9635         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9636         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9637     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9638         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9639         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9640            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9641         board[toY][toX] = newPiece;
9642     }
9643     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9644                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9645         // [HGM] superchess: take promotion piece out of holdings
9646         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9647         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9648             if(!--board[k][BOARD_WIDTH-2])
9649                 board[k][BOARD_WIDTH-1] = EmptySquare;
9650         } else {
9651             if(!--board[BOARD_HEIGHT-1-k][1])
9652                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9653         }
9654     }
9655
9656 }
9657
9658 /* Updates forwardMostMove */
9659 void
9660 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9661 {
9662 //    forwardMostMove++; // [HGM] bare: moved downstream
9663
9664     (void) CoordsToAlgebraic(boards[forwardMostMove],
9665                              PosFlags(forwardMostMove),
9666                              fromY, fromX, toY, toX, promoChar,
9667                              parseList[forwardMostMove]);
9668
9669     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9670         int timeLeft; static int lastLoadFlag=0; int king, piece;
9671         piece = boards[forwardMostMove][fromY][fromX];
9672         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9673         if(gameInfo.variant == VariantKnightmate)
9674             king += (int) WhiteUnicorn - (int) WhiteKing;
9675         if(forwardMostMove == 0) {
9676             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9677                 fprintf(serverMoves, "%s;", UserName());
9678             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9679                 fprintf(serverMoves, "%s;", second.tidy);
9680             fprintf(serverMoves, "%s;", first.tidy);
9681             if(gameMode == MachinePlaysWhite)
9682                 fprintf(serverMoves, "%s;", UserName());
9683             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9684                 fprintf(serverMoves, "%s;", second.tidy);
9685         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9686         lastLoadFlag = loadFlag;
9687         // print base move
9688         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9689         // print castling suffix
9690         if( toY == fromY && piece == king ) {
9691             if(toX-fromX > 1)
9692                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9693             if(fromX-toX >1)
9694                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9695         }
9696         // e.p. suffix
9697         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9698              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9699              boards[forwardMostMove][toY][toX] == EmptySquare
9700              && fromX != toX && fromY != toY)
9701                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9702         // promotion suffix
9703         if(promoChar != NULLCHAR) {
9704             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9705                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9706                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9707             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9708         }
9709         if(!loadFlag) {
9710                 char buf[MOVE_LEN*2], *p; int len;
9711             fprintf(serverMoves, "/%d/%d",
9712                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9713             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9714             else                      timeLeft = blackTimeRemaining/1000;
9715             fprintf(serverMoves, "/%d", timeLeft);
9716                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9717                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9718                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9719                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9720             fprintf(serverMoves, "/%s", buf);
9721         }
9722         fflush(serverMoves);
9723     }
9724
9725     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9726         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9727       return;
9728     }
9729     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9730     if (commentList[forwardMostMove+1] != NULL) {
9731         free(commentList[forwardMostMove+1]);
9732         commentList[forwardMostMove+1] = NULL;
9733     }
9734     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9735     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9736     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9737     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9738     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9739     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9740     adjustedClock = FALSE;
9741     gameInfo.result = GameUnfinished;
9742     if (gameInfo.resultDetails != NULL) {
9743         free(gameInfo.resultDetails);
9744         gameInfo.resultDetails = NULL;
9745     }
9746     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9747                               moveList[forwardMostMove - 1]);
9748     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9749       case MT_NONE:
9750       case MT_STALEMATE:
9751       default:
9752         break;
9753       case MT_CHECK:
9754         if(gameInfo.variant != VariantShogi)
9755             strcat(parseList[forwardMostMove - 1], "+");
9756         break;
9757       case MT_CHECKMATE:
9758       case MT_STAINMATE:
9759         strcat(parseList[forwardMostMove - 1], "#");
9760         break;
9761     }
9762
9763 }
9764
9765 /* Updates currentMove if not pausing */
9766 void
9767 ShowMove (int fromX, int fromY, int toX, int toY)
9768 {
9769     int instant = (gameMode == PlayFromGameFile) ?
9770         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9771     if(appData.noGUI) return;
9772     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9773         if (!instant) {
9774             if (forwardMostMove == currentMove + 1) {
9775                 AnimateMove(boards[forwardMostMove - 1],
9776                             fromX, fromY, toX, toY);
9777             }
9778         }
9779         currentMove = forwardMostMove;
9780     }
9781
9782     if (instant) return;
9783
9784     DisplayMove(currentMove - 1);
9785     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9786             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9787                 SetHighlights(fromX, fromY, toX, toY);
9788             }
9789     }
9790     DrawPosition(FALSE, boards[currentMove]);
9791     DisplayBothClocks();
9792     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9793 }
9794
9795 void
9796 SendEgtPath (ChessProgramState *cps)
9797 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9798         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9799
9800         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9801
9802         while(*p) {
9803             char c, *q = name+1, *r, *s;
9804
9805             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9806             while(*p && *p != ',') *q++ = *p++;
9807             *q++ = ':'; *q = 0;
9808             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9809                 strcmp(name, ",nalimov:") == 0 ) {
9810                 // take nalimov path from the menu-changeable option first, if it is defined
9811               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9812                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9813             } else
9814             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9815                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9816                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9817                 s = r = StrStr(s, ":") + 1; // beginning of path info
9818                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9819                 c = *r; *r = 0;             // temporarily null-terminate path info
9820                     *--q = 0;               // strip of trailig ':' from name
9821                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9822                 *r = c;
9823                 SendToProgram(buf,cps);     // send egtbpath command for this format
9824             }
9825             if(*p == ',') p++; // read away comma to position for next format name
9826         }
9827 }
9828
9829 void
9830 InitChessProgram (ChessProgramState *cps, int setup)
9831 /* setup needed to setup FRC opening position */
9832 {
9833     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9834     if (appData.noChessProgram) return;
9835     hintRequested = FALSE;
9836     bookRequested = FALSE;
9837
9838     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9839     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9840     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9841     if(cps->memSize) { /* [HGM] memory */
9842       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9843         SendToProgram(buf, cps);
9844     }
9845     SendEgtPath(cps); /* [HGM] EGT */
9846     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9847       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9848         SendToProgram(buf, cps);
9849     }
9850
9851     SendToProgram(cps->initString, cps);
9852     if (gameInfo.variant != VariantNormal &&
9853         gameInfo.variant != VariantLoadable
9854         /* [HGM] also send variant if board size non-standard */
9855         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9856                                             ) {
9857       char *v = VariantName(gameInfo.variant);
9858       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9859         /* [HGM] in protocol 1 we have to assume all variants valid */
9860         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9861         DisplayFatalError(buf, 0, 1);
9862         return;
9863       }
9864
9865       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9866       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9867       if( gameInfo.variant == VariantXiangqi )
9868            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9869       if( gameInfo.variant == VariantShogi )
9870            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9871       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9872            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9873       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9874           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9875            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9876       if( gameInfo.variant == VariantCourier )
9877            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9878       if( gameInfo.variant == VariantSuper )
9879            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9880       if( gameInfo.variant == VariantGreat )
9881            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9882       if( gameInfo.variant == VariantSChess )
9883            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9884       if( gameInfo.variant == VariantGrand )
9885            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9886
9887       if(overruled) {
9888         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9889                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9890            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9891            if(StrStr(cps->variants, b) == NULL) {
9892                // specific sized variant not known, check if general sizing allowed
9893                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9894                    if(StrStr(cps->variants, "boardsize") == NULL) {
9895                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9896                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9897                        DisplayFatalError(buf, 0, 1);
9898                        return;
9899                    }
9900                    /* [HGM] here we really should compare with the maximum supported board size */
9901                }
9902            }
9903       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9904       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9905       SendToProgram(buf, cps);
9906     }
9907     currentlyInitializedVariant = gameInfo.variant;
9908
9909     /* [HGM] send opening position in FRC to first engine */
9910     if(setup) {
9911           SendToProgram("force\n", cps);
9912           SendBoard(cps, 0);
9913           /* engine is now in force mode! Set flag to wake it up after first move. */
9914           setboardSpoiledMachineBlack = 1;
9915     }
9916
9917     if (cps->sendICS) {
9918       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9919       SendToProgram(buf, cps);
9920     }
9921     cps->maybeThinking = FALSE;
9922     cps->offeredDraw = 0;
9923     if (!appData.icsActive) {
9924         SendTimeControl(cps, movesPerSession, timeControl,
9925                         timeIncrement, appData.searchDepth,
9926                         searchTime);
9927     }
9928     if (appData.showThinking
9929         // [HGM] thinking: four options require thinking output to be sent
9930         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9931                                 ) {
9932         SendToProgram("post\n", cps);
9933     }
9934     SendToProgram("hard\n", cps);
9935     if (!appData.ponderNextMove) {
9936         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9937            it without being sure what state we are in first.  "hard"
9938            is not a toggle, so that one is OK.
9939          */
9940         SendToProgram("easy\n", cps);
9941     }
9942     if (cps->usePing) {
9943       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9944       SendToProgram(buf, cps);
9945     }
9946     cps->initDone = TRUE;
9947     ClearEngineOutputPane(cps == &second);
9948 }
9949
9950
9951 void
9952 ResendOptions (ChessProgramState *cps)
9953 { // send the stored value of the options
9954   int i;
9955   char buf[MSG_SIZ];
9956   Option *opt = cps->option;
9957   for(i=0; i<cps->nrOptions; i++, opt++) {
9958       switch(opt->type) {
9959         case Spin:
9960         case Slider:
9961         case CheckBox:
9962             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9963           break;
9964         case ComboBox:
9965           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9966           break;
9967         default:
9968             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9969           break;
9970         case Button:
9971         case SaveButton:
9972           continue;
9973       }
9974       SendToProgram(buf, cps);
9975   }
9976 }
9977
9978 void
9979 StartChessProgram (ChessProgramState *cps)
9980 {
9981     char buf[MSG_SIZ];
9982     int err;
9983
9984     if (appData.noChessProgram) return;
9985     cps->initDone = FALSE;
9986
9987     if (strcmp(cps->host, "localhost") == 0) {
9988         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9989     } else if (*appData.remoteShell == NULLCHAR) {
9990         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9991     } else {
9992         if (*appData.remoteUser == NULLCHAR) {
9993           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9994                     cps->program);
9995         } else {
9996           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9997                     cps->host, appData.remoteUser, cps->program);
9998         }
9999         err = StartChildProcess(buf, "", &cps->pr);
10000     }
10001
10002     if (err != 0) {
10003       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10004         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10005         if(cps != &first) return;
10006         appData.noChessProgram = TRUE;
10007         ThawUI();
10008         SetNCPMode();
10009 //      DisplayFatalError(buf, err, 1);
10010 //      cps->pr = NoProc;
10011 //      cps->isr = NULL;
10012         return;
10013     }
10014
10015     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10016     if (cps->protocolVersion > 1) {
10017       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10018       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10019         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10020         cps->comboCnt = 0;  //                and values of combo boxes
10021       }
10022       SendToProgram(buf, cps);
10023       if(cps->reload) ResendOptions(cps);
10024     } else {
10025       SendToProgram("xboard\n", cps);
10026     }
10027 }
10028
10029 void
10030 TwoMachinesEventIfReady P((void))
10031 {
10032   static int curMess = 0;
10033   if (first.lastPing != first.lastPong || !first.initDone) {
10034     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10035     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10036     return;
10037   }
10038   if (second.lastPing != second.lastPong) {
10039     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10040     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10041     return;
10042   }
10043   DisplayMessage("", ""); curMess = 0;
10044   ThawUI();
10045   TwoMachinesEvent();
10046 }
10047
10048 char *
10049 MakeName (char *template)
10050 {
10051     time_t clock;
10052     struct tm *tm;
10053     static char buf[MSG_SIZ];
10054     char *p = buf;
10055     int i;
10056
10057     clock = time((time_t *)NULL);
10058     tm = localtime(&clock);
10059
10060     while(*p++ = *template++) if(p[-1] == '%') {
10061         switch(*template++) {
10062           case 0:   *p = 0; return buf;
10063           case 'Y': i = tm->tm_year+1900; break;
10064           case 'y': i = tm->tm_year-100; break;
10065           case 'M': i = tm->tm_mon+1; break;
10066           case 'd': i = tm->tm_mday; break;
10067           case 'h': i = tm->tm_hour; break;
10068           case 'm': i = tm->tm_min; break;
10069           case 's': i = tm->tm_sec; break;
10070           default:  i = 0;
10071         }
10072         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10073     }
10074     return buf;
10075 }
10076
10077 int
10078 CountPlayers (char *p)
10079 {
10080     int n = 0;
10081     while(p = strchr(p, '\n')) p++, n++; // count participants
10082     return n;
10083 }
10084
10085 FILE *
10086 WriteTourneyFile (char *results, FILE *f)
10087 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10088     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10089     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10090         // create a file with tournament description
10091         fprintf(f, "-participants {%s}\n", appData.participants);
10092         fprintf(f, "-seedBase %d\n", appData.seedBase);
10093         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10094         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10095         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10096         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10097         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10098         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10099         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10100         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10101         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10102         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10103         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10104         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10105         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10106         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10107         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10108         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10109         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10110         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10111         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10112         fprintf(f, "-smpCores %d\n", appData.smpCores);
10113         if(searchTime > 0)
10114                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10115         else {
10116                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10117                 fprintf(f, "-tc %s\n", appData.timeControl);
10118                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10119         }
10120         fprintf(f, "-results \"%s\"\n", results);
10121     }
10122     return f;
10123 }
10124
10125 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10126
10127 void
10128 Substitute (char *participants, int expunge)
10129 {
10130     int i, changed, changes=0, nPlayers=0;
10131     char *p, *q, *r, buf[MSG_SIZ];
10132     if(participants == NULL) return;
10133     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10134     r = p = participants; q = appData.participants;
10135     while(*p && *p == *q) {
10136         if(*p == '\n') r = p+1, nPlayers++;
10137         p++; q++;
10138     }
10139     if(*p) { // difference
10140         while(*p && *p++ != '\n');
10141         while(*q && *q++ != '\n');
10142       changed = nPlayers;
10143         changes = 1 + (strcmp(p, q) != 0);
10144     }
10145     if(changes == 1) { // a single engine mnemonic was changed
10146         q = r; while(*q) nPlayers += (*q++ == '\n');
10147         p = buf; while(*r && (*p = *r++) != '\n') p++;
10148         *p = NULLCHAR;
10149         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10150         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10151         if(mnemonic[i]) { // The substitute is valid
10152             FILE *f;
10153             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10154                 flock(fileno(f), LOCK_EX);
10155                 ParseArgsFromFile(f);
10156                 fseek(f, 0, SEEK_SET);
10157                 FREE(appData.participants); appData.participants = participants;
10158                 if(expunge) { // erase results of replaced engine
10159                     int len = strlen(appData.results), w, b, dummy;
10160                     for(i=0; i<len; i++) {
10161                         Pairing(i, nPlayers, &w, &b, &dummy);
10162                         if((w == changed || b == changed) && appData.results[i] == '*') {
10163                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10164                             fclose(f);
10165                             return;
10166                         }
10167                     }
10168                     for(i=0; i<len; i++) {
10169                         Pairing(i, nPlayers, &w, &b, &dummy);
10170                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10171                     }
10172                 }
10173                 WriteTourneyFile(appData.results, f);
10174                 fclose(f); // release lock
10175                 return;
10176             }
10177         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10178     }
10179     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10180     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10181     free(participants);
10182     return;
10183 }
10184
10185 int
10186 CheckPlayers (char *participants)
10187 {
10188         int i;
10189         char buf[MSG_SIZ], *p;
10190         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10191         while(p = strchr(participants, '\n')) {
10192             *p = NULLCHAR;
10193             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10194             if(!mnemonic[i]) {
10195                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10196                 *p = '\n';
10197                 DisplayError(buf, 0);
10198                 return 1;
10199             }
10200             *p = '\n';
10201             participants = p + 1;
10202         }
10203         return 0;
10204 }
10205
10206 int
10207 CreateTourney (char *name)
10208 {
10209         FILE *f;
10210         if(matchMode && strcmp(name, appData.tourneyFile)) {
10211              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10212         }
10213         if(name[0] == NULLCHAR) {
10214             if(appData.participants[0])
10215                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10216             return 0;
10217         }
10218         f = fopen(name, "r");
10219         if(f) { // file exists
10220             ASSIGN(appData.tourneyFile, name);
10221             ParseArgsFromFile(f); // parse it
10222         } else {
10223             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10224             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10225                 DisplayError(_("Not enough participants"), 0);
10226                 return 0;
10227             }
10228             if(CheckPlayers(appData.participants)) return 0;
10229             ASSIGN(appData.tourneyFile, name);
10230             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10231             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10232         }
10233         fclose(f);
10234         appData.noChessProgram = FALSE;
10235         appData.clockMode = TRUE;
10236         SetGNUMode();
10237         return 1;
10238 }
10239
10240 int
10241 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10242 {
10243     char buf[MSG_SIZ], *p, *q;
10244     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10245     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10246     skip = !all && group[0]; // if group requested, we start in skip mode
10247     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10248         p = names; q = buf; header = 0;
10249         while(*p && *p != '\n') *q++ = *p++;
10250         *q = 0;
10251         if(*p == '\n') p++;
10252         if(buf[0] == '#') {
10253             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10254             depth++; // we must be entering a new group
10255             if(all) continue; // suppress printing group headers when complete list requested
10256             header = 1;
10257             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10258         }
10259         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10260         if(engineList[i]) free(engineList[i]);
10261         engineList[i] = strdup(buf);
10262         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10263         if(engineMnemonic[i]) free(engineMnemonic[i]);
10264         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10265             strcat(buf, " (");
10266             sscanf(q + 8, "%s", buf + strlen(buf));
10267             strcat(buf, ")");
10268         }
10269         engineMnemonic[i] = strdup(buf);
10270         i++;
10271     }
10272     engineList[i] = engineMnemonic[i] = NULL;
10273     return i;
10274 }
10275
10276 // following implemented as macro to avoid type limitations
10277 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10278
10279 void
10280 SwapEngines (int n)
10281 {   // swap settings for first engine and other engine (so far only some selected options)
10282     int h;
10283     char *p;
10284     if(n == 0) return;
10285     SWAP(directory, p)
10286     SWAP(chessProgram, p)
10287     SWAP(isUCI, h)
10288     SWAP(hasOwnBookUCI, h)
10289     SWAP(protocolVersion, h)
10290     SWAP(reuse, h)
10291     SWAP(scoreIsAbsolute, h)
10292     SWAP(timeOdds, h)
10293     SWAP(logo, p)
10294     SWAP(pgnName, p)
10295     SWAP(pvSAN, h)
10296     SWAP(engOptions, p)
10297     SWAP(engInitString, p)
10298     SWAP(computerString, p)
10299     SWAP(features, p)
10300     SWAP(fenOverride, p)
10301     SWAP(NPS, h)
10302     SWAP(accumulateTC, h)
10303     SWAP(host, p)
10304 }
10305
10306 int
10307 GetEngineLine (char *s, int n)
10308 {
10309     int i;
10310     char buf[MSG_SIZ];
10311     extern char *icsNames;
10312     if(!s || !*s) return 0;
10313     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10314     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10315     if(!mnemonic[i]) return 0;
10316     if(n == 11) return 1; // just testing if there was a match
10317     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10318     if(n == 1) SwapEngines(n);
10319     ParseArgsFromString(buf);
10320     if(n == 1) SwapEngines(n);
10321     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10322         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10323         ParseArgsFromString(buf);
10324     }
10325     return 1;
10326 }
10327
10328 int
10329 SetPlayer (int player, char *p)
10330 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10331     int i;
10332     char buf[MSG_SIZ], *engineName;
10333     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10334     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10335     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10336     if(mnemonic[i]) {
10337         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10338         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10339         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10340         ParseArgsFromString(buf);
10341     }
10342     free(engineName);
10343     return i;
10344 }
10345
10346 char *recentEngines;
10347
10348 void
10349 RecentEngineEvent (int nr)
10350 {
10351     int n;
10352 //    SwapEngines(1); // bump first to second
10353 //    ReplaceEngine(&second, 1); // and load it there
10354     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10355     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10356     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10357         ReplaceEngine(&first, 0);
10358         FloatToFront(&appData.recentEngineList, command[n]);
10359     }
10360 }
10361
10362 int
10363 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10364 {   // determine players from game number
10365     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10366
10367     if(appData.tourneyType == 0) {
10368         roundsPerCycle = (nPlayers - 1) | 1;
10369         pairingsPerRound = nPlayers / 2;
10370     } else if(appData.tourneyType > 0) {
10371         roundsPerCycle = nPlayers - appData.tourneyType;
10372         pairingsPerRound = appData.tourneyType;
10373     }
10374     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10375     gamesPerCycle = gamesPerRound * roundsPerCycle;
10376     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10377     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10378     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10379     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10380     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10381     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10382
10383     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10384     if(appData.roundSync) *syncInterval = gamesPerRound;
10385
10386     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10387
10388     if(appData.tourneyType == 0) {
10389         if(curPairing == (nPlayers-1)/2 ) {
10390             *whitePlayer = curRound;
10391             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10392         } else {
10393             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10394             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10395             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10396             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10397         }
10398     } else if(appData.tourneyType > 1) {
10399         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10400         *whitePlayer = curRound + appData.tourneyType;
10401     } else if(appData.tourneyType > 0) {
10402         *whitePlayer = curPairing;
10403         *blackPlayer = curRound + appData.tourneyType;
10404     }
10405
10406     // take care of white/black alternation per round.
10407     // For cycles and games this is already taken care of by default, derived from matchGame!
10408     return curRound & 1;
10409 }
10410
10411 int
10412 NextTourneyGame (int nr, int *swapColors)
10413 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10414     char *p, *q;
10415     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10416     FILE *tf;
10417     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10418     tf = fopen(appData.tourneyFile, "r");
10419     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10420     ParseArgsFromFile(tf); fclose(tf);
10421     InitTimeControls(); // TC might be altered from tourney file
10422
10423     nPlayers = CountPlayers(appData.participants); // count participants
10424     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10425     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10426
10427     if(syncInterval) {
10428         p = q = appData.results;
10429         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10430         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10431             DisplayMessage(_("Waiting for other game(s)"),"");
10432             waitingForGame = TRUE;
10433             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10434             return 0;
10435         }
10436         waitingForGame = FALSE;
10437     }
10438
10439     if(appData.tourneyType < 0) {
10440         if(nr>=0 && !pairingReceived) {
10441             char buf[1<<16];
10442             if(pairing.pr == NoProc) {
10443                 if(!appData.pairingEngine[0]) {
10444                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10445                     return 0;
10446                 }
10447                 StartChessProgram(&pairing); // starts the pairing engine
10448             }
10449             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10450             SendToProgram(buf, &pairing);
10451             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10452             SendToProgram(buf, &pairing);
10453             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10454         }
10455         pairingReceived = 0;                              // ... so we continue here
10456         *swapColors = 0;
10457         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10458         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10459         matchGame = 1; roundNr = nr / syncInterval + 1;
10460     }
10461
10462     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10463
10464     // redefine engines, engine dir, etc.
10465     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10466     if(first.pr == NoProc) {
10467       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10468       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10469     }
10470     if(second.pr == NoProc) {
10471       SwapEngines(1);
10472       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10473       SwapEngines(1);         // and make that valid for second engine by swapping
10474       InitEngine(&second, 1);
10475     }
10476     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10477     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10478     return 1;
10479 }
10480
10481 void
10482 NextMatchGame ()
10483 {   // performs game initialization that does not invoke engines, and then tries to start the game
10484     int res, firstWhite, swapColors = 0;
10485     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10486     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
10487         char buf[MSG_SIZ];
10488         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10489         if(strcmp(buf, currentDebugFile)) { // name has changed
10490             FILE *f = fopen(buf, "w");
10491             if(f) { // if opening the new file failed, just keep using the old one
10492                 ASSIGN(currentDebugFile, buf);
10493                 fclose(debugFP);
10494                 debugFP = f;
10495             }
10496             if(appData.serverFileName) {
10497                 if(serverFP) fclose(serverFP);
10498                 serverFP = fopen(appData.serverFileName, "w");
10499                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10500                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10501             }
10502         }
10503     }
10504     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10505     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10506     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10507     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10508     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10509     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10510     Reset(FALSE, first.pr != NoProc);
10511     res = LoadGameOrPosition(matchGame); // setup game
10512     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10513     if(!res) return; // abort when bad game/pos file
10514     TwoMachinesEvent();
10515 }
10516
10517 void
10518 UserAdjudicationEvent (int result)
10519 {
10520     ChessMove gameResult = GameIsDrawn;
10521
10522     if( result > 0 ) {
10523         gameResult = WhiteWins;
10524     }
10525     else if( result < 0 ) {
10526         gameResult = BlackWins;
10527     }
10528
10529     if( gameMode == TwoMachinesPlay ) {
10530         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10531     }
10532 }
10533
10534
10535 // [HGM] save: calculate checksum of game to make games easily identifiable
10536 int
10537 StringCheckSum (char *s)
10538 {
10539         int i = 0;
10540         if(s==NULL) return 0;
10541         while(*s) i = i*259 + *s++;
10542         return i;
10543 }
10544
10545 int
10546 GameCheckSum ()
10547 {
10548         int i, sum=0;
10549         for(i=backwardMostMove; i<forwardMostMove; i++) {
10550                 sum += pvInfoList[i].depth;
10551                 sum += StringCheckSum(parseList[i]);
10552                 sum += StringCheckSum(commentList[i]);
10553                 sum *= 261;
10554         }
10555         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10556         return sum + StringCheckSum(commentList[i]);
10557 } // end of save patch
10558
10559 void
10560 GameEnds (ChessMove result, char *resultDetails, int whosays)
10561 {
10562     GameMode nextGameMode;
10563     int isIcsGame;
10564     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10565
10566     if(endingGame) return; /* [HGM] crash: forbid recursion */
10567     endingGame = 1;
10568     if(twoBoards) { // [HGM] dual: switch back to one board
10569         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10570         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10571     }
10572     if (appData.debugMode) {
10573       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10574               result, resultDetails ? resultDetails : "(null)", whosays);
10575     }
10576
10577     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10578
10579     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10580
10581     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10582         /* If we are playing on ICS, the server decides when the
10583            game is over, but the engine can offer to draw, claim
10584            a draw, or resign.
10585          */
10586 #if ZIPPY
10587         if (appData.zippyPlay && first.initDone) {
10588             if (result == GameIsDrawn) {
10589                 /* In case draw still needs to be claimed */
10590                 SendToICS(ics_prefix);
10591                 SendToICS("draw\n");
10592             } else if (StrCaseStr(resultDetails, "resign")) {
10593                 SendToICS(ics_prefix);
10594                 SendToICS("resign\n");
10595             }
10596         }
10597 #endif
10598         endingGame = 0; /* [HGM] crash */
10599         return;
10600     }
10601
10602     /* If we're loading the game from a file, stop */
10603     if (whosays == GE_FILE) {
10604       (void) StopLoadGameTimer();
10605       gameFileFP = NULL;
10606     }
10607
10608     /* Cancel draw offers */
10609     first.offeredDraw = second.offeredDraw = 0;
10610
10611     /* If this is an ICS game, only ICS can really say it's done;
10612        if not, anyone can. */
10613     isIcsGame = (gameMode == IcsPlayingWhite ||
10614                  gameMode == IcsPlayingBlack ||
10615                  gameMode == IcsObserving    ||
10616                  gameMode == IcsExamining);
10617
10618     if (!isIcsGame || whosays == GE_ICS) {
10619         /* OK -- not an ICS game, or ICS said it was done */
10620         StopClocks();
10621         if (!isIcsGame && !appData.noChessProgram)
10622           SetUserThinkingEnables();
10623
10624         /* [HGM] if a machine claims the game end we verify this claim */
10625         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10626             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10627                 char claimer;
10628                 ChessMove trueResult = (ChessMove) -1;
10629
10630                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10631                                             first.twoMachinesColor[0] :
10632                                             second.twoMachinesColor[0] ;
10633
10634                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10635                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10636                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10637                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10638                 } else
10639                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10640                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10641                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10642                 } else
10643                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10644                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10645                 }
10646
10647                 // now verify win claims, but not in drop games, as we don't understand those yet
10648                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10649                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10650                     (result == WhiteWins && claimer == 'w' ||
10651                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10652                       if (appData.debugMode) {
10653                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10654                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10655                       }
10656                       if(result != trueResult) {
10657                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10658                               result = claimer == 'w' ? BlackWins : WhiteWins;
10659                               resultDetails = buf;
10660                       }
10661                 } else
10662                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10663                     && (forwardMostMove <= backwardMostMove ||
10664                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10665                         (claimer=='b')==(forwardMostMove&1))
10666                                                                                   ) {
10667                       /* [HGM] verify: draws that were not flagged are false claims */
10668                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10669                       result = claimer == 'w' ? BlackWins : WhiteWins;
10670                       resultDetails = buf;
10671                 }
10672                 /* (Claiming a loss is accepted no questions asked!) */
10673             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10674                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10675                 result = GameUnfinished;
10676                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10677             }
10678             /* [HGM] bare: don't allow bare King to win */
10679             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10680                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10681                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10682                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10683                && result != GameIsDrawn)
10684             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10685                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10686                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10687                         if(p >= 0 && p <= (int)WhiteKing) k++;
10688                 }
10689                 if (appData.debugMode) {
10690                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10691                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10692                 }
10693                 if(k <= 1) {
10694                         result = GameIsDrawn;
10695                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10696                         resultDetails = buf;
10697                 }
10698             }
10699         }
10700
10701
10702         if(serverMoves != NULL && !loadFlag) { char c = '=';
10703             if(result==WhiteWins) c = '+';
10704             if(result==BlackWins) c = '-';
10705             if(resultDetails != NULL)
10706                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10707         }
10708         if (resultDetails != NULL) {
10709             gameInfo.result = result;
10710             gameInfo.resultDetails = StrSave(resultDetails);
10711
10712             /* display last move only if game was not loaded from file */
10713             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10714                 DisplayMove(currentMove - 1);
10715
10716             if (forwardMostMove != 0) {
10717                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10718                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10719                                                                 ) {
10720                     if (*appData.saveGameFile != NULLCHAR) {
10721                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10722                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10723                         else
10724                         SaveGameToFile(appData.saveGameFile, TRUE);
10725                     } else if (appData.autoSaveGames) {
10726                         AutoSaveGame();
10727                     }
10728                     if (*appData.savePositionFile != NULLCHAR) {
10729                         SavePositionToFile(appData.savePositionFile);
10730                     }
10731                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10732                 }
10733             }
10734
10735             /* Tell program how game ended in case it is learning */
10736             /* [HGM] Moved this to after saving the PGN, just in case */
10737             /* engine died and we got here through time loss. In that */
10738             /* case we will get a fatal error writing the pipe, which */
10739             /* would otherwise lose us the PGN.                       */
10740             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10741             /* output during GameEnds should never be fatal anymore   */
10742             if (gameMode == MachinePlaysWhite ||
10743                 gameMode == MachinePlaysBlack ||
10744                 gameMode == TwoMachinesPlay ||
10745                 gameMode == IcsPlayingWhite ||
10746                 gameMode == IcsPlayingBlack ||
10747                 gameMode == BeginningOfGame) {
10748                 char buf[MSG_SIZ];
10749                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10750                         resultDetails);
10751                 if (first.pr != NoProc) {
10752                     SendToProgram(buf, &first);
10753                 }
10754                 if (second.pr != NoProc &&
10755                     gameMode == TwoMachinesPlay) {
10756                     SendToProgram(buf, &second);
10757                 }
10758             }
10759         }
10760
10761         if (appData.icsActive) {
10762             if (appData.quietPlay &&
10763                 (gameMode == IcsPlayingWhite ||
10764                  gameMode == IcsPlayingBlack)) {
10765                 SendToICS(ics_prefix);
10766                 SendToICS("set shout 1\n");
10767             }
10768             nextGameMode = IcsIdle;
10769             ics_user_moved = FALSE;
10770             /* clean up premove.  It's ugly when the game has ended and the
10771              * premove highlights are still on the board.
10772              */
10773             if (gotPremove) {
10774               gotPremove = FALSE;
10775               ClearPremoveHighlights();
10776               DrawPosition(FALSE, boards[currentMove]);
10777             }
10778             if (whosays == GE_ICS) {
10779                 switch (result) {
10780                 case WhiteWins:
10781                     if (gameMode == IcsPlayingWhite)
10782                         PlayIcsWinSound();
10783                     else if(gameMode == IcsPlayingBlack)
10784                         PlayIcsLossSound();
10785                     break;
10786                 case BlackWins:
10787                     if (gameMode == IcsPlayingBlack)
10788                         PlayIcsWinSound();
10789                     else if(gameMode == IcsPlayingWhite)
10790                         PlayIcsLossSound();
10791                     break;
10792                 case GameIsDrawn:
10793                     PlayIcsDrawSound();
10794                     break;
10795                 default:
10796                     PlayIcsUnfinishedSound();
10797                 }
10798             }
10799         } else if (gameMode == EditGame ||
10800                    gameMode == PlayFromGameFile ||
10801                    gameMode == AnalyzeMode ||
10802                    gameMode == AnalyzeFile) {
10803             nextGameMode = gameMode;
10804         } else {
10805             nextGameMode = EndOfGame;
10806         }
10807         pausing = FALSE;
10808         ModeHighlight();
10809     } else {
10810         nextGameMode = gameMode;
10811     }
10812
10813     if (appData.noChessProgram) {
10814         gameMode = nextGameMode;
10815         ModeHighlight();
10816         endingGame = 0; /* [HGM] crash */
10817         return;
10818     }
10819
10820     if (first.reuse) {
10821         /* Put first chess program into idle state */
10822         if (first.pr != NoProc &&
10823             (gameMode == MachinePlaysWhite ||
10824              gameMode == MachinePlaysBlack ||
10825              gameMode == TwoMachinesPlay ||
10826              gameMode == IcsPlayingWhite ||
10827              gameMode == IcsPlayingBlack ||
10828              gameMode == BeginningOfGame)) {
10829             SendToProgram("force\n", &first);
10830             if (first.usePing) {
10831               char buf[MSG_SIZ];
10832               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10833               SendToProgram(buf, &first);
10834             }
10835         }
10836     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10837         /* Kill off first chess program */
10838         if (first.isr != NULL)
10839           RemoveInputSource(first.isr);
10840         first.isr = NULL;
10841
10842         if (first.pr != NoProc) {
10843             ExitAnalyzeMode();
10844             DoSleep( appData.delayBeforeQuit );
10845             SendToProgram("quit\n", &first);
10846             DoSleep( appData.delayAfterQuit );
10847             DestroyChildProcess(first.pr, first.useSigterm);
10848             first.reload = TRUE;
10849         }
10850         first.pr = NoProc;
10851     }
10852     if (second.reuse) {
10853         /* Put second chess program into idle state */
10854         if (second.pr != NoProc &&
10855             gameMode == TwoMachinesPlay) {
10856             SendToProgram("force\n", &second);
10857             if (second.usePing) {
10858               char buf[MSG_SIZ];
10859               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10860               SendToProgram(buf, &second);
10861             }
10862         }
10863     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10864         /* Kill off second chess program */
10865         if (second.isr != NULL)
10866           RemoveInputSource(second.isr);
10867         second.isr = NULL;
10868
10869         if (second.pr != NoProc) {
10870             DoSleep( appData.delayBeforeQuit );
10871             SendToProgram("quit\n", &second);
10872             DoSleep( appData.delayAfterQuit );
10873             DestroyChildProcess(second.pr, second.useSigterm);
10874             second.reload = TRUE;
10875         }
10876         second.pr = NoProc;
10877     }
10878
10879     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10880         char resChar = '=';
10881         switch (result) {
10882         case WhiteWins:
10883           resChar = '+';
10884           if (first.twoMachinesColor[0] == 'w') {
10885             first.matchWins++;
10886           } else {
10887             second.matchWins++;
10888           }
10889           break;
10890         case BlackWins:
10891           resChar = '-';
10892           if (first.twoMachinesColor[0] == 'b') {
10893             first.matchWins++;
10894           } else {
10895             second.matchWins++;
10896           }
10897           break;
10898         case GameUnfinished:
10899           resChar = ' ';
10900         default:
10901           break;
10902         }
10903
10904         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10905         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10906             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10907             ReserveGame(nextGame, resChar); // sets nextGame
10908             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10909             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10910         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10911
10912         if (nextGame <= appData.matchGames && !abortMatch) {
10913             gameMode = nextGameMode;
10914             matchGame = nextGame; // this will be overruled in tourney mode!
10915             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10916             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10917             endingGame = 0; /* [HGM] crash */
10918             return;
10919         } else {
10920             gameMode = nextGameMode;
10921             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10922                      first.tidy, second.tidy,
10923                      first.matchWins, second.matchWins,
10924                      appData.matchGames - (first.matchWins + second.matchWins));
10925             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10926             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10927             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10928             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10929                 first.twoMachinesColor = "black\n";
10930                 second.twoMachinesColor = "white\n";
10931             } else {
10932                 first.twoMachinesColor = "white\n";
10933                 second.twoMachinesColor = "black\n";
10934             }
10935         }
10936     }
10937     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10938         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10939       ExitAnalyzeMode();
10940     gameMode = nextGameMode;
10941     ModeHighlight();
10942     endingGame = 0;  /* [HGM] crash */
10943     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10944         if(matchMode == TRUE) { // match through command line: exit with or without popup
10945             if(ranking) {
10946                 ToNrEvent(forwardMostMove);
10947                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10948                 else ExitEvent(0);
10949             } else DisplayFatalError(buf, 0, 0);
10950         } else { // match through menu; just stop, with or without popup
10951             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10952             ModeHighlight();
10953             if(ranking){
10954                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10955             } else DisplayNote(buf);
10956       }
10957       if(ranking) free(ranking);
10958     }
10959 }
10960
10961 /* Assumes program was just initialized (initString sent).
10962    Leaves program in force mode. */
10963 void
10964 FeedMovesToProgram (ChessProgramState *cps, int upto)
10965 {
10966     int i;
10967
10968     if (appData.debugMode)
10969       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10970               startedFromSetupPosition ? "position and " : "",
10971               backwardMostMove, upto, cps->which);
10972     if(currentlyInitializedVariant != gameInfo.variant) {
10973       char buf[MSG_SIZ];
10974         // [HGM] variantswitch: make engine aware of new variant
10975         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10976                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10977         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10978         SendToProgram(buf, cps);
10979         currentlyInitializedVariant = gameInfo.variant;
10980     }
10981     SendToProgram("force\n", cps);
10982     if (startedFromSetupPosition) {
10983         SendBoard(cps, backwardMostMove);
10984     if (appData.debugMode) {
10985         fprintf(debugFP, "feedMoves\n");
10986     }
10987     }
10988     for (i = backwardMostMove; i < upto; i++) {
10989         SendMoveToProgram(i, cps);
10990     }
10991 }
10992
10993
10994 int
10995 ResurrectChessProgram ()
10996 {
10997      /* The chess program may have exited.
10998         If so, restart it and feed it all the moves made so far. */
10999     static int doInit = 0;
11000
11001     if (appData.noChessProgram) return 1;
11002
11003     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11004         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
11005         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11006         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11007     } else {
11008         if (first.pr != NoProc) return 1;
11009         StartChessProgram(&first);
11010     }
11011     InitChessProgram(&first, FALSE);
11012     FeedMovesToProgram(&first, currentMove);
11013
11014     if (!first.sendTime) {
11015         /* can't tell gnuchess what its clock should read,
11016            so we bow to its notion. */
11017         ResetClocks();
11018         timeRemaining[0][currentMove] = whiteTimeRemaining;
11019         timeRemaining[1][currentMove] = blackTimeRemaining;
11020     }
11021
11022     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11023                 appData.icsEngineAnalyze) && first.analysisSupport) {
11024       SendToProgram("analyze\n", &first);
11025       first.analyzing = TRUE;
11026     }
11027     return 1;
11028 }
11029
11030 /*
11031  * Button procedures
11032  */
11033 void
11034 Reset (int redraw, int init)
11035 {
11036     int i;
11037
11038     if (appData.debugMode) {
11039         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11040                 redraw, init, gameMode);
11041     }
11042     CleanupTail(); // [HGM] vari: delete any stored variations
11043     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11044     pausing = pauseExamInvalid = FALSE;
11045     startedFromSetupPosition = blackPlaysFirst = FALSE;
11046     firstMove = TRUE;
11047     whiteFlag = blackFlag = FALSE;
11048     userOfferedDraw = FALSE;
11049     hintRequested = bookRequested = FALSE;
11050     first.maybeThinking = FALSE;
11051     second.maybeThinking = FALSE;
11052     first.bookSuspend = FALSE; // [HGM] book
11053     second.bookSuspend = FALSE;
11054     thinkOutput[0] = NULLCHAR;
11055     lastHint[0] = NULLCHAR;
11056     ClearGameInfo(&gameInfo);
11057     gameInfo.variant = StringToVariant(appData.variant);
11058     ics_user_moved = ics_clock_paused = FALSE;
11059     ics_getting_history = H_FALSE;
11060     ics_gamenum = -1;
11061     white_holding[0] = black_holding[0] = NULLCHAR;
11062     ClearProgramStats();
11063     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11064
11065     ResetFrontEnd();
11066     ClearHighlights();
11067     flipView = appData.flipView;
11068     ClearPremoveHighlights();
11069     gotPremove = FALSE;
11070     alarmSounded = FALSE;
11071
11072     GameEnds(EndOfFile, NULL, GE_PLAYER);
11073     if(appData.serverMovesName != NULL) {
11074         /* [HGM] prepare to make moves file for broadcasting */
11075         clock_t t = clock();
11076         if(serverMoves != NULL) fclose(serverMoves);
11077         serverMoves = fopen(appData.serverMovesName, "r");
11078         if(serverMoves != NULL) {
11079             fclose(serverMoves);
11080             /* delay 15 sec before overwriting, so all clients can see end */
11081             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11082         }
11083         serverMoves = fopen(appData.serverMovesName, "w");
11084     }
11085
11086     ExitAnalyzeMode();
11087     gameMode = BeginningOfGame;
11088     ModeHighlight();
11089     if(appData.icsActive) gameInfo.variant = VariantNormal;
11090     currentMove = forwardMostMove = backwardMostMove = 0;
11091     MarkTargetSquares(1);
11092     InitPosition(redraw);
11093     for (i = 0; i < MAX_MOVES; i++) {
11094         if (commentList[i] != NULL) {
11095             free(commentList[i]);
11096             commentList[i] = NULL;
11097         }
11098     }
11099     ResetClocks();
11100     timeRemaining[0][0] = whiteTimeRemaining;
11101     timeRemaining[1][0] = blackTimeRemaining;
11102
11103     if (first.pr == NoProc) {
11104         StartChessProgram(&first);
11105     }
11106     if (init) {
11107             InitChessProgram(&first, startedFromSetupPosition);
11108     }
11109     DisplayTitle("");
11110     DisplayMessage("", "");
11111     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11112     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11113     ClearMap();        // [HGM] exclude: invalidate map
11114 }
11115
11116 void
11117 AutoPlayGameLoop ()
11118 {
11119     for (;;) {
11120         if (!AutoPlayOneMove())
11121           return;
11122         if (matchMode || appData.timeDelay == 0)
11123           continue;
11124         if (appData.timeDelay < 0)
11125           return;
11126         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11127         break;
11128     }
11129 }
11130
11131 void
11132 AnalyzeNextGame()
11133 {
11134     ReloadGame(1); // next game
11135 }
11136
11137 int
11138 AutoPlayOneMove ()
11139 {
11140     int fromX, fromY, toX, toY;
11141
11142     if (appData.debugMode) {
11143       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11144     }
11145
11146     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11147       return FALSE;
11148
11149     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11150       pvInfoList[currentMove].depth = programStats.depth;
11151       pvInfoList[currentMove].score = programStats.score;
11152       pvInfoList[currentMove].time  = 0;
11153       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11154     }
11155
11156     if (currentMove >= forwardMostMove) {
11157       if(gameMode == AnalyzeFile) {
11158           if(appData.loadGameIndex == -1) {
11159             GameEnds(EndOfFile, NULL, GE_FILE);
11160           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11161           } else {
11162           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11163         }
11164       }
11165 //      gameMode = EndOfGame;
11166 //      ModeHighlight();
11167
11168       /* [AS] Clear current move marker at the end of a game */
11169       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11170
11171       return FALSE;
11172     }
11173
11174     toX = moveList[currentMove][2] - AAA;
11175     toY = moveList[currentMove][3] - ONE;
11176
11177     if (moveList[currentMove][1] == '@') {
11178         if (appData.highlightLastMove) {
11179             SetHighlights(-1, -1, toX, toY);
11180         }
11181     } else {
11182         fromX = moveList[currentMove][0] - AAA;
11183         fromY = moveList[currentMove][1] - ONE;
11184
11185         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11186
11187         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11188
11189         if (appData.highlightLastMove) {
11190             SetHighlights(fromX, fromY, toX, toY);
11191         }
11192     }
11193     DisplayMove(currentMove);
11194     SendMoveToProgram(currentMove++, &first);
11195     DisplayBothClocks();
11196     DrawPosition(FALSE, boards[currentMove]);
11197     // [HGM] PV info: always display, routine tests if empty
11198     DisplayComment(currentMove - 1, commentList[currentMove]);
11199     return TRUE;
11200 }
11201
11202
11203 int
11204 LoadGameOneMove (ChessMove readAhead)
11205 {
11206     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11207     char promoChar = NULLCHAR;
11208     ChessMove moveType;
11209     char move[MSG_SIZ];
11210     char *p, *q;
11211
11212     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11213         gameMode != AnalyzeMode && gameMode != Training) {
11214         gameFileFP = NULL;
11215         return FALSE;
11216     }
11217
11218     yyboardindex = forwardMostMove;
11219     if (readAhead != EndOfFile) {
11220       moveType = readAhead;
11221     } else {
11222       if (gameFileFP == NULL)
11223           return FALSE;
11224       moveType = (ChessMove) Myylex();
11225     }
11226
11227     done = FALSE;
11228     switch (moveType) {
11229       case Comment:
11230         if (appData.debugMode)
11231           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11232         p = yy_text;
11233
11234         /* append the comment but don't display it */
11235         AppendComment(currentMove, p, FALSE);
11236         return TRUE;
11237
11238       case WhiteCapturesEnPassant:
11239       case BlackCapturesEnPassant:
11240       case WhitePromotion:
11241       case BlackPromotion:
11242       case WhiteNonPromotion:
11243       case BlackNonPromotion:
11244       case NormalMove:
11245       case WhiteKingSideCastle:
11246       case WhiteQueenSideCastle:
11247       case BlackKingSideCastle:
11248       case BlackQueenSideCastle:
11249       case WhiteKingSideCastleWild:
11250       case WhiteQueenSideCastleWild:
11251       case BlackKingSideCastleWild:
11252       case BlackQueenSideCastleWild:
11253       /* PUSH Fabien */
11254       case WhiteHSideCastleFR:
11255       case WhiteASideCastleFR:
11256       case BlackHSideCastleFR:
11257       case BlackASideCastleFR:
11258       /* POP Fabien */
11259         if (appData.debugMode)
11260           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11261         fromX = currentMoveString[0] - AAA;
11262         fromY = currentMoveString[1] - ONE;
11263         toX = currentMoveString[2] - AAA;
11264         toY = currentMoveString[3] - ONE;
11265         promoChar = currentMoveString[4];
11266         break;
11267
11268       case WhiteDrop:
11269       case BlackDrop:
11270         if (appData.debugMode)
11271           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11272         fromX = moveType == WhiteDrop ?
11273           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11274         (int) CharToPiece(ToLower(currentMoveString[0]));
11275         fromY = DROP_RANK;
11276         toX = currentMoveString[2] - AAA;
11277         toY = currentMoveString[3] - ONE;
11278         break;
11279
11280       case WhiteWins:
11281       case BlackWins:
11282       case GameIsDrawn:
11283       case GameUnfinished:
11284         if (appData.debugMode)
11285           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11286         p = strchr(yy_text, '{');
11287         if (p == NULL) p = strchr(yy_text, '(');
11288         if (p == NULL) {
11289             p = yy_text;
11290             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11291         } else {
11292             q = strchr(p, *p == '{' ? '}' : ')');
11293             if (q != NULL) *q = NULLCHAR;
11294             p++;
11295         }
11296         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11297         GameEnds(moveType, p, GE_FILE);
11298         done = TRUE;
11299         if (cmailMsgLoaded) {
11300             ClearHighlights();
11301             flipView = WhiteOnMove(currentMove);
11302             if (moveType == GameUnfinished) flipView = !flipView;
11303             if (appData.debugMode)
11304               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11305         }
11306         break;
11307
11308       case EndOfFile:
11309         if (appData.debugMode)
11310           fprintf(debugFP, "Parser hit end of file\n");
11311         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11312           case MT_NONE:
11313           case MT_CHECK:
11314             break;
11315           case MT_CHECKMATE:
11316           case MT_STAINMATE:
11317             if (WhiteOnMove(currentMove)) {
11318                 GameEnds(BlackWins, "Black mates", GE_FILE);
11319             } else {
11320                 GameEnds(WhiteWins, "White mates", GE_FILE);
11321             }
11322             break;
11323           case MT_STALEMATE:
11324             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11325             break;
11326         }
11327         done = TRUE;
11328         break;
11329
11330       case MoveNumberOne:
11331         if (lastLoadGameStart == GNUChessGame) {
11332             /* GNUChessGames have numbers, but they aren't move numbers */
11333             if (appData.debugMode)
11334               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11335                       yy_text, (int) moveType);
11336             return LoadGameOneMove(EndOfFile); /* tail recursion */
11337         }
11338         /* else fall thru */
11339
11340       case XBoardGame:
11341       case GNUChessGame:
11342       case PGNTag:
11343         /* Reached start of next game in file */
11344         if (appData.debugMode)
11345           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11346         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11347           case MT_NONE:
11348           case MT_CHECK:
11349             break;
11350           case MT_CHECKMATE:
11351           case MT_STAINMATE:
11352             if (WhiteOnMove(currentMove)) {
11353                 GameEnds(BlackWins, "Black mates", GE_FILE);
11354             } else {
11355                 GameEnds(WhiteWins, "White mates", GE_FILE);
11356             }
11357             break;
11358           case MT_STALEMATE:
11359             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11360             break;
11361         }
11362         done = TRUE;
11363         break;
11364
11365       case PositionDiagram:     /* should not happen; ignore */
11366       case ElapsedTime:         /* ignore */
11367       case NAG:                 /* ignore */
11368         if (appData.debugMode)
11369           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11370                   yy_text, (int) moveType);
11371         return LoadGameOneMove(EndOfFile); /* tail recursion */
11372
11373       case IllegalMove:
11374         if (appData.testLegality) {
11375             if (appData.debugMode)
11376               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11377             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11378                     (forwardMostMove / 2) + 1,
11379                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11380             DisplayError(move, 0);
11381             done = TRUE;
11382         } else {
11383             if (appData.debugMode)
11384               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11385                       yy_text, currentMoveString);
11386             fromX = currentMoveString[0] - AAA;
11387             fromY = currentMoveString[1] - ONE;
11388             toX = currentMoveString[2] - AAA;
11389             toY = currentMoveString[3] - ONE;
11390             promoChar = currentMoveString[4];
11391         }
11392         break;
11393
11394       case AmbiguousMove:
11395         if (appData.debugMode)
11396           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11397         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11398                 (forwardMostMove / 2) + 1,
11399                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11400         DisplayError(move, 0);
11401         done = TRUE;
11402         break;
11403
11404       default:
11405       case ImpossibleMove:
11406         if (appData.debugMode)
11407           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11408         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11409                 (forwardMostMove / 2) + 1,
11410                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11411         DisplayError(move, 0);
11412         done = TRUE;
11413         break;
11414     }
11415
11416     if (done) {
11417         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11418             DrawPosition(FALSE, boards[currentMove]);
11419             DisplayBothClocks();
11420             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11421               DisplayComment(currentMove - 1, commentList[currentMove]);
11422         }
11423         (void) StopLoadGameTimer();
11424         gameFileFP = NULL;
11425         cmailOldMove = forwardMostMove;
11426         return FALSE;
11427     } else {
11428         /* currentMoveString is set as a side-effect of yylex */
11429
11430         thinkOutput[0] = NULLCHAR;
11431         MakeMove(fromX, fromY, toX, toY, promoChar);
11432         currentMove = forwardMostMove;
11433         return TRUE;
11434     }
11435 }
11436
11437 /* Load the nth game from the given file */
11438 int
11439 LoadGameFromFile (char *filename, int n, char *title, int useList)
11440 {
11441     FILE *f;
11442     char buf[MSG_SIZ];
11443
11444     if (strcmp(filename, "-") == 0) {
11445         f = stdin;
11446         title = "stdin";
11447     } else {
11448         f = fopen(filename, "rb");
11449         if (f == NULL) {
11450           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11451             DisplayError(buf, errno);
11452             return FALSE;
11453         }
11454     }
11455     if (fseek(f, 0, 0) == -1) {
11456         /* f is not seekable; probably a pipe */
11457         useList = FALSE;
11458     }
11459     if (useList && n == 0) {
11460         int error = GameListBuild(f);
11461         if (error) {
11462             DisplayError(_("Cannot build game list"), error);
11463         } else if (!ListEmpty(&gameList) &&
11464                    ((ListGame *) gameList.tailPred)->number > 1) {
11465             GameListPopUp(f, title);
11466             return TRUE;
11467         }
11468         GameListDestroy();
11469         n = 1;
11470     }
11471     if (n == 0) n = 1;
11472     return LoadGame(f, n, title, FALSE);
11473 }
11474
11475
11476 void
11477 MakeRegisteredMove ()
11478 {
11479     int fromX, fromY, toX, toY;
11480     char promoChar;
11481     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11482         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11483           case CMAIL_MOVE:
11484           case CMAIL_DRAW:
11485             if (appData.debugMode)
11486               fprintf(debugFP, "Restoring %s for game %d\n",
11487                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11488
11489             thinkOutput[0] = NULLCHAR;
11490             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11491             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11492             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11493             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11494             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11495             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11496             MakeMove(fromX, fromY, toX, toY, promoChar);
11497             ShowMove(fromX, fromY, toX, toY);
11498
11499             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11500               case MT_NONE:
11501               case MT_CHECK:
11502                 break;
11503
11504               case MT_CHECKMATE:
11505               case MT_STAINMATE:
11506                 if (WhiteOnMove(currentMove)) {
11507                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11508                 } else {
11509                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11510                 }
11511                 break;
11512
11513               case MT_STALEMATE:
11514                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11515                 break;
11516             }
11517
11518             break;
11519
11520           case CMAIL_RESIGN:
11521             if (WhiteOnMove(currentMove)) {
11522                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11523             } else {
11524                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11525             }
11526             break;
11527
11528           case CMAIL_ACCEPT:
11529             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11530             break;
11531
11532           default:
11533             break;
11534         }
11535     }
11536
11537     return;
11538 }
11539
11540 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11541 int
11542 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11543 {
11544     int retVal;
11545
11546     if (gameNumber > nCmailGames) {
11547         DisplayError(_("No more games in this message"), 0);
11548         return FALSE;
11549     }
11550     if (f == lastLoadGameFP) {
11551         int offset = gameNumber - lastLoadGameNumber;
11552         if (offset == 0) {
11553             cmailMsg[0] = NULLCHAR;
11554             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11555                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11556                 nCmailMovesRegistered--;
11557             }
11558             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11559             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11560                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11561             }
11562         } else {
11563             if (! RegisterMove()) return FALSE;
11564         }
11565     }
11566
11567     retVal = LoadGame(f, gameNumber, title, useList);
11568
11569     /* Make move registered during previous look at this game, if any */
11570     MakeRegisteredMove();
11571
11572     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11573         commentList[currentMove]
11574           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11575         DisplayComment(currentMove - 1, commentList[currentMove]);
11576     }
11577
11578     return retVal;
11579 }
11580
11581 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11582 int
11583 ReloadGame (int offset)
11584 {
11585     int gameNumber = lastLoadGameNumber + offset;
11586     if (lastLoadGameFP == NULL) {
11587         DisplayError(_("No game has been loaded yet"), 0);
11588         return FALSE;
11589     }
11590     if (gameNumber <= 0) {
11591         DisplayError(_("Can't back up any further"), 0);
11592         return FALSE;
11593     }
11594     if (cmailMsgLoaded) {
11595         return CmailLoadGame(lastLoadGameFP, gameNumber,
11596                              lastLoadGameTitle, lastLoadGameUseList);
11597     } else {
11598         return LoadGame(lastLoadGameFP, gameNumber,
11599                         lastLoadGameTitle, lastLoadGameUseList);
11600     }
11601 }
11602
11603 int keys[EmptySquare+1];
11604
11605 int
11606 PositionMatches (Board b1, Board b2)
11607 {
11608     int r, f, sum=0;
11609     switch(appData.searchMode) {
11610         case 1: return CompareWithRights(b1, b2);
11611         case 2:
11612             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11613                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11614             }
11615             return TRUE;
11616         case 3:
11617             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11618               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11619                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11620             }
11621             return sum==0;
11622         case 4:
11623             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11624                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11625             }
11626             return sum==0;
11627     }
11628     return TRUE;
11629 }
11630
11631 #define Q_PROMO  4
11632 #define Q_EP     3
11633 #define Q_BCASTL 2
11634 #define Q_WCASTL 1
11635
11636 int pieceList[256], quickBoard[256];
11637 ChessSquare pieceType[256] = { EmptySquare };
11638 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11639 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11640 int soughtTotal, turn;
11641 Boolean epOK, flipSearch;
11642
11643 typedef struct {
11644     unsigned char piece, to;
11645 } Move;
11646
11647 #define DSIZE (250000)
11648
11649 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11650 Move *moveDatabase = initialSpace;
11651 unsigned int movePtr, dataSize = DSIZE;
11652
11653 int
11654 MakePieceList (Board board, int *counts)
11655 {
11656     int r, f, n=Q_PROMO, total=0;
11657     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11658     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11659         int sq = f + (r<<4);
11660         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11661             quickBoard[sq] = ++n;
11662             pieceList[n] = sq;
11663             pieceType[n] = board[r][f];
11664             counts[board[r][f]]++;
11665             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11666             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11667             total++;
11668         }
11669     }
11670     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11671     return total;
11672 }
11673
11674 void
11675 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11676 {
11677     int sq = fromX + (fromY<<4);
11678     int piece = quickBoard[sq];
11679     quickBoard[sq] = 0;
11680     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11681     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11682         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11683         moveDatabase[movePtr++].piece = Q_WCASTL;
11684         quickBoard[sq] = piece;
11685         piece = quickBoard[from]; quickBoard[from] = 0;
11686         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11687     } else
11688     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11689         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11690         moveDatabase[movePtr++].piece = Q_BCASTL;
11691         quickBoard[sq] = piece;
11692         piece = quickBoard[from]; quickBoard[from] = 0;
11693         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11694     } else
11695     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11696         quickBoard[(fromY<<4)+toX] = 0;
11697         moveDatabase[movePtr].piece = Q_EP;
11698         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11699         moveDatabase[movePtr].to = sq;
11700     } else
11701     if(promoPiece != pieceType[piece]) {
11702         moveDatabase[movePtr++].piece = Q_PROMO;
11703         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11704     }
11705     moveDatabase[movePtr].piece = piece;
11706     quickBoard[sq] = piece;
11707     movePtr++;
11708 }
11709
11710 int
11711 PackGame (Board board)
11712 {
11713     Move *newSpace = NULL;
11714     moveDatabase[movePtr].piece = 0; // terminate previous game
11715     if(movePtr > dataSize) {
11716         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11717         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11718         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11719         if(newSpace) {
11720             int i;
11721             Move *p = moveDatabase, *q = newSpace;
11722             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11723             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11724             moveDatabase = newSpace;
11725         } else { // calloc failed, we must be out of memory. Too bad...
11726             dataSize = 0; // prevent calloc events for all subsequent games
11727             return 0;     // and signal this one isn't cached
11728         }
11729     }
11730     movePtr++;
11731     MakePieceList(board, counts);
11732     return movePtr;
11733 }
11734
11735 int
11736 QuickCompare (Board board, int *minCounts, int *maxCounts)
11737 {   // compare according to search mode
11738     int r, f;
11739     switch(appData.searchMode)
11740     {
11741       case 1: // exact position match
11742         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11743         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11744             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11745         }
11746         break;
11747       case 2: // can have extra material on empty squares
11748         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11749             if(board[r][f] == EmptySquare) continue;
11750             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11751         }
11752         break;
11753       case 3: // material with exact Pawn structure
11754         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11755             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11756             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11757         } // fall through to material comparison
11758       case 4: // exact material
11759         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11760         break;
11761       case 6: // material range with given imbalance
11762         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11763         // fall through to range comparison
11764       case 5: // material range
11765         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11766     }
11767     return TRUE;
11768 }
11769
11770 int
11771 QuickScan (Board board, Move *move)
11772 {   // reconstruct game,and compare all positions in it
11773     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11774     do {
11775         int piece = move->piece;
11776         int to = move->to, from = pieceList[piece];
11777         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11778           if(!piece) return -1;
11779           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11780             piece = (++move)->piece;
11781             from = pieceList[piece];
11782             counts[pieceType[piece]]--;
11783             pieceType[piece] = (ChessSquare) move->to;
11784             counts[move->to]++;
11785           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11786             counts[pieceType[quickBoard[to]]]--;
11787             quickBoard[to] = 0; total--;
11788             move++;
11789             continue;
11790           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11791             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11792             from  = pieceList[piece]; // so this must be King
11793             quickBoard[from] = 0;
11794             pieceList[piece] = to;
11795             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11796             quickBoard[from] = 0; // rook
11797             quickBoard[to] = piece;
11798             to = move->to; piece = move->piece;
11799             goto aftercastle;
11800           }
11801         }
11802         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11803         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11804         quickBoard[from] = 0;
11805       aftercastle:
11806         quickBoard[to] = piece;
11807         pieceList[piece] = to;
11808         cnt++; turn ^= 3;
11809         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11810            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11811            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11812                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11813           ) {
11814             static int lastCounts[EmptySquare+1];
11815             int i;
11816             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11817             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11818         } else stretch = 0;
11819         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11820         move++;
11821     } while(1);
11822 }
11823
11824 void
11825 InitSearch ()
11826 {
11827     int r, f;
11828     flipSearch = FALSE;
11829     CopyBoard(soughtBoard, boards[currentMove]);
11830     soughtTotal = MakePieceList(soughtBoard, maxSought);
11831     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11832     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11833     CopyBoard(reverseBoard, boards[currentMove]);
11834     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11835         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11836         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11837         reverseBoard[r][f] = piece;
11838     }
11839     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11840     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11841     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11842                  || (boards[currentMove][CASTLING][2] == NoRights ||
11843                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11844                  && (boards[currentMove][CASTLING][5] == NoRights ||
11845                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11846       ) {
11847         flipSearch = TRUE;
11848         CopyBoard(flipBoard, soughtBoard);
11849         CopyBoard(rotateBoard, reverseBoard);
11850         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11851             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11852             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11853         }
11854     }
11855     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11856     if(appData.searchMode >= 5) {
11857         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11858         MakePieceList(soughtBoard, minSought);
11859         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11860     }
11861     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11862         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11863 }
11864
11865 GameInfo dummyInfo;
11866 static int creatingBook;
11867
11868 int
11869 GameContainsPosition (FILE *f, ListGame *lg)
11870 {
11871     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11872     int fromX, fromY, toX, toY;
11873     char promoChar;
11874     static int initDone=FALSE;
11875
11876     // weed out games based on numerical tag comparison
11877     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11878     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11879     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11880     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11881     if(!initDone) {
11882         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11883         initDone = TRUE;
11884     }
11885     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11886     else CopyBoard(boards[scratch], initialPosition); // default start position
11887     if(lg->moves) {
11888         turn = btm + 1;
11889         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11890         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11891     }
11892     if(btm) plyNr++;
11893     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11894     fseek(f, lg->offset, 0);
11895     yynewfile(f);
11896     while(1) {
11897         yyboardindex = scratch;
11898         quickFlag = plyNr+1;
11899         next = Myylex();
11900         quickFlag = 0;
11901         switch(next) {
11902             case PGNTag:
11903                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11904             default:
11905                 continue;
11906
11907             case XBoardGame:
11908             case GNUChessGame:
11909                 if(plyNr) return -1; // after we have seen moves, this is for new game
11910               continue;
11911
11912             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11913             case ImpossibleMove:
11914             case WhiteWins: // game ends here with these four
11915             case BlackWins:
11916             case GameIsDrawn:
11917             case GameUnfinished:
11918                 return -1;
11919
11920             case IllegalMove:
11921                 if(appData.testLegality) return -1;
11922             case WhiteCapturesEnPassant:
11923             case BlackCapturesEnPassant:
11924             case WhitePromotion:
11925             case BlackPromotion:
11926             case WhiteNonPromotion:
11927             case BlackNonPromotion:
11928             case NormalMove:
11929             case WhiteKingSideCastle:
11930             case WhiteQueenSideCastle:
11931             case BlackKingSideCastle:
11932             case BlackQueenSideCastle:
11933             case WhiteKingSideCastleWild:
11934             case WhiteQueenSideCastleWild:
11935             case BlackKingSideCastleWild:
11936             case BlackQueenSideCastleWild:
11937             case WhiteHSideCastleFR:
11938             case WhiteASideCastleFR:
11939             case BlackHSideCastleFR:
11940             case BlackASideCastleFR:
11941                 fromX = currentMoveString[0] - AAA;
11942                 fromY = currentMoveString[1] - ONE;
11943                 toX = currentMoveString[2] - AAA;
11944                 toY = currentMoveString[3] - ONE;
11945                 promoChar = currentMoveString[4];
11946                 break;
11947             case WhiteDrop:
11948             case BlackDrop:
11949                 fromX = next == WhiteDrop ?
11950                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11951                   (int) CharToPiece(ToLower(currentMoveString[0]));
11952                 fromY = DROP_RANK;
11953                 toX = currentMoveString[2] - AAA;
11954                 toY = currentMoveString[3] - ONE;
11955                 promoChar = 0;
11956                 break;
11957         }
11958         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11959         plyNr++;
11960         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11961         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11962         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11963         if(appData.findMirror) {
11964             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11965             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11966         }
11967     }
11968 }
11969
11970 /* Load the nth game from open file f */
11971 int
11972 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11973 {
11974     ChessMove cm;
11975     char buf[MSG_SIZ];
11976     int gn = gameNumber;
11977     ListGame *lg = NULL;
11978     int numPGNTags = 0;
11979     int err, pos = -1;
11980     GameMode oldGameMode;
11981     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11982
11983     if (appData.debugMode)
11984         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11985
11986     if (gameMode == Training )
11987         SetTrainingModeOff();
11988
11989     oldGameMode = gameMode;
11990     if (gameMode != BeginningOfGame) {
11991       Reset(FALSE, TRUE);
11992     }
11993
11994     gameFileFP = f;
11995     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11996         fclose(lastLoadGameFP);
11997     }
11998
11999     if (useList) {
12000         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12001
12002         if (lg) {
12003             fseek(f, lg->offset, 0);
12004             GameListHighlight(gameNumber);
12005             pos = lg->position;
12006             gn = 1;
12007         }
12008         else {
12009             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12010               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12011             else
12012             DisplayError(_("Game number out of range"), 0);
12013             return FALSE;
12014         }
12015     } else {
12016         GameListDestroy();
12017         if (fseek(f, 0, 0) == -1) {
12018             if (f == lastLoadGameFP ?
12019                 gameNumber == lastLoadGameNumber + 1 :
12020                 gameNumber == 1) {
12021                 gn = 1;
12022             } else {
12023                 DisplayError(_("Can't seek on game file"), 0);
12024                 return FALSE;
12025             }
12026         }
12027     }
12028     lastLoadGameFP = f;
12029     lastLoadGameNumber = gameNumber;
12030     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12031     lastLoadGameUseList = useList;
12032
12033     yynewfile(f);
12034
12035     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12036       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12037                 lg->gameInfo.black);
12038             DisplayTitle(buf);
12039     } else if (*title != NULLCHAR) {
12040         if (gameNumber > 1) {
12041           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12042             DisplayTitle(buf);
12043         } else {
12044             DisplayTitle(title);
12045         }
12046     }
12047
12048     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12049         gameMode = PlayFromGameFile;
12050         ModeHighlight();
12051     }
12052
12053     currentMove = forwardMostMove = backwardMostMove = 0;
12054     CopyBoard(boards[0], initialPosition);
12055     StopClocks();
12056
12057     /*
12058      * Skip the first gn-1 games in the file.
12059      * Also skip over anything that precedes an identifiable
12060      * start of game marker, to avoid being confused by
12061      * garbage at the start of the file.  Currently
12062      * recognized start of game markers are the move number "1",
12063      * the pattern "gnuchess .* game", the pattern
12064      * "^[#;%] [^ ]* game file", and a PGN tag block.
12065      * A game that starts with one of the latter two patterns
12066      * will also have a move number 1, possibly
12067      * following a position diagram.
12068      * 5-4-02: Let's try being more lenient and allowing a game to
12069      * start with an unnumbered move.  Does that break anything?
12070      */
12071     cm = lastLoadGameStart = EndOfFile;
12072     while (gn > 0) {
12073         yyboardindex = forwardMostMove;
12074         cm = (ChessMove) Myylex();
12075         switch (cm) {
12076           case EndOfFile:
12077             if (cmailMsgLoaded) {
12078                 nCmailGames = CMAIL_MAX_GAMES - gn;
12079             } else {
12080                 Reset(TRUE, TRUE);
12081                 DisplayError(_("Game not found in file"), 0);
12082             }
12083             return FALSE;
12084
12085           case GNUChessGame:
12086           case XBoardGame:
12087             gn--;
12088             lastLoadGameStart = cm;
12089             break;
12090
12091           case MoveNumberOne:
12092             switch (lastLoadGameStart) {
12093               case GNUChessGame:
12094               case XBoardGame:
12095               case PGNTag:
12096                 break;
12097               case MoveNumberOne:
12098               case EndOfFile:
12099                 gn--;           /* count this game */
12100                 lastLoadGameStart = cm;
12101                 break;
12102               default:
12103                 /* impossible */
12104                 break;
12105             }
12106             break;
12107
12108           case PGNTag:
12109             switch (lastLoadGameStart) {
12110               case GNUChessGame:
12111               case PGNTag:
12112               case MoveNumberOne:
12113               case EndOfFile:
12114                 gn--;           /* count this game */
12115                 lastLoadGameStart = cm;
12116                 break;
12117               case XBoardGame:
12118                 lastLoadGameStart = cm; /* game counted already */
12119                 break;
12120               default:
12121                 /* impossible */
12122                 break;
12123             }
12124             if (gn > 0) {
12125                 do {
12126                     yyboardindex = forwardMostMove;
12127                     cm = (ChessMove) Myylex();
12128                 } while (cm == PGNTag || cm == Comment);
12129             }
12130             break;
12131
12132           case WhiteWins:
12133           case BlackWins:
12134           case GameIsDrawn:
12135             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12136                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12137                     != CMAIL_OLD_RESULT) {
12138                     nCmailResults ++ ;
12139                     cmailResult[  CMAIL_MAX_GAMES
12140                                 - gn - 1] = CMAIL_OLD_RESULT;
12141                 }
12142             }
12143             break;
12144
12145           case NormalMove:
12146             /* Only a NormalMove can be at the start of a game
12147              * without a position diagram. */
12148             if (lastLoadGameStart == EndOfFile ) {
12149               gn--;
12150               lastLoadGameStart = MoveNumberOne;
12151             }
12152             break;
12153
12154           default:
12155             break;
12156         }
12157     }
12158
12159     if (appData.debugMode)
12160       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12161
12162     if (cm == XBoardGame) {
12163         /* Skip any header junk before position diagram and/or move 1 */
12164         for (;;) {
12165             yyboardindex = forwardMostMove;
12166             cm = (ChessMove) Myylex();
12167
12168             if (cm == EndOfFile ||
12169                 cm == GNUChessGame || cm == XBoardGame) {
12170                 /* Empty game; pretend end-of-file and handle later */
12171                 cm = EndOfFile;
12172                 break;
12173             }
12174
12175             if (cm == MoveNumberOne || cm == PositionDiagram ||
12176                 cm == PGNTag || cm == Comment)
12177               break;
12178         }
12179     } else if (cm == GNUChessGame) {
12180         if (gameInfo.event != NULL) {
12181             free(gameInfo.event);
12182         }
12183         gameInfo.event = StrSave(yy_text);
12184     }
12185
12186     startedFromSetupPosition = FALSE;
12187     while (cm == PGNTag) {
12188         if (appData.debugMode)
12189           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12190         err = ParsePGNTag(yy_text, &gameInfo);
12191         if (!err) numPGNTags++;
12192
12193         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12194         if(gameInfo.variant != oldVariant) {
12195             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12196             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12197             InitPosition(TRUE);
12198             oldVariant = gameInfo.variant;
12199             if (appData.debugMode)
12200               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12201         }
12202
12203
12204         if (gameInfo.fen != NULL) {
12205           Board initial_position;
12206           startedFromSetupPosition = TRUE;
12207           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12208             Reset(TRUE, TRUE);
12209             DisplayError(_("Bad FEN position in file"), 0);
12210             return FALSE;
12211           }
12212           CopyBoard(boards[0], initial_position);
12213           if (blackPlaysFirst) {
12214             currentMove = forwardMostMove = backwardMostMove = 1;
12215             CopyBoard(boards[1], initial_position);
12216             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12217             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12218             timeRemaining[0][1] = whiteTimeRemaining;
12219             timeRemaining[1][1] = blackTimeRemaining;
12220             if (commentList[0] != NULL) {
12221               commentList[1] = commentList[0];
12222               commentList[0] = NULL;
12223             }
12224           } else {
12225             currentMove = forwardMostMove = backwardMostMove = 0;
12226           }
12227           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12228           {   int i;
12229               initialRulePlies = FENrulePlies;
12230               for( i=0; i< nrCastlingRights; i++ )
12231                   initialRights[i] = initial_position[CASTLING][i];
12232           }
12233           yyboardindex = forwardMostMove;
12234           free(gameInfo.fen);
12235           gameInfo.fen = NULL;
12236         }
12237
12238         yyboardindex = forwardMostMove;
12239         cm = (ChessMove) Myylex();
12240
12241         /* Handle comments interspersed among the tags */
12242         while (cm == Comment) {
12243             char *p;
12244             if (appData.debugMode)
12245               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12246             p = yy_text;
12247             AppendComment(currentMove, p, FALSE);
12248             yyboardindex = forwardMostMove;
12249             cm = (ChessMove) Myylex();
12250         }
12251     }
12252
12253     /* don't rely on existence of Event tag since if game was
12254      * pasted from clipboard the Event tag may not exist
12255      */
12256     if (numPGNTags > 0){
12257         char *tags;
12258         if (gameInfo.variant == VariantNormal) {
12259           VariantClass v = StringToVariant(gameInfo.event);
12260           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12261           if(v < VariantShogi) gameInfo.variant = v;
12262         }
12263         if (!matchMode) {
12264           if( appData.autoDisplayTags ) {
12265             tags = PGNTags(&gameInfo);
12266             TagsPopUp(tags, CmailMsg());
12267             free(tags);
12268           }
12269         }
12270     } else {
12271         /* Make something up, but don't display it now */
12272         SetGameInfo();
12273         TagsPopDown();
12274     }
12275
12276     if (cm == PositionDiagram) {
12277         int i, j;
12278         char *p;
12279         Board initial_position;
12280
12281         if (appData.debugMode)
12282           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12283
12284         if (!startedFromSetupPosition) {
12285             p = yy_text;
12286             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12287               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12288                 switch (*p) {
12289                   case '{':
12290                   case '[':
12291                   case '-':
12292                   case ' ':
12293                   case '\t':
12294                   case '\n':
12295                   case '\r':
12296                     break;
12297                   default:
12298                     initial_position[i][j++] = CharToPiece(*p);
12299                     break;
12300                 }
12301             while (*p == ' ' || *p == '\t' ||
12302                    *p == '\n' || *p == '\r') p++;
12303
12304             if (strncmp(p, "black", strlen("black"))==0)
12305               blackPlaysFirst = TRUE;
12306             else
12307               blackPlaysFirst = FALSE;
12308             startedFromSetupPosition = TRUE;
12309
12310             CopyBoard(boards[0], initial_position);
12311             if (blackPlaysFirst) {
12312                 currentMove = forwardMostMove = backwardMostMove = 1;
12313                 CopyBoard(boards[1], initial_position);
12314                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12315                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12316                 timeRemaining[0][1] = whiteTimeRemaining;
12317                 timeRemaining[1][1] = blackTimeRemaining;
12318                 if (commentList[0] != NULL) {
12319                     commentList[1] = commentList[0];
12320                     commentList[0] = NULL;
12321                 }
12322             } else {
12323                 currentMove = forwardMostMove = backwardMostMove = 0;
12324             }
12325         }
12326         yyboardindex = forwardMostMove;
12327         cm = (ChessMove) Myylex();
12328     }
12329
12330   if(!creatingBook) {
12331     if (first.pr == NoProc) {
12332         StartChessProgram(&first);
12333     }
12334     InitChessProgram(&first, FALSE);
12335     SendToProgram("force\n", &first);
12336     if (startedFromSetupPosition) {
12337         SendBoard(&first, forwardMostMove);
12338     if (appData.debugMode) {
12339         fprintf(debugFP, "Load Game\n");
12340     }
12341         DisplayBothClocks();
12342     }
12343   }
12344
12345     /* [HGM] server: flag to write setup moves in broadcast file as one */
12346     loadFlag = appData.suppressLoadMoves;
12347
12348     while (cm == Comment) {
12349         char *p;
12350         if (appData.debugMode)
12351           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12352         p = yy_text;
12353         AppendComment(currentMove, p, FALSE);
12354         yyboardindex = forwardMostMove;
12355         cm = (ChessMove) Myylex();
12356     }
12357
12358     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12359         cm == WhiteWins || cm == BlackWins ||
12360         cm == GameIsDrawn || cm == GameUnfinished) {
12361         DisplayMessage("", _("No moves in game"));
12362         if (cmailMsgLoaded) {
12363             if (appData.debugMode)
12364               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12365             ClearHighlights();
12366             flipView = FALSE;
12367         }
12368         DrawPosition(FALSE, boards[currentMove]);
12369         DisplayBothClocks();
12370         gameMode = EditGame;
12371         ModeHighlight();
12372         gameFileFP = NULL;
12373         cmailOldMove = 0;
12374         return TRUE;
12375     }
12376
12377     // [HGM] PV info: routine tests if comment empty
12378     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12379         DisplayComment(currentMove - 1, commentList[currentMove]);
12380     }
12381     if (!matchMode && appData.timeDelay != 0)
12382       DrawPosition(FALSE, boards[currentMove]);
12383
12384     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12385       programStats.ok_to_send = 1;
12386     }
12387
12388     /* if the first token after the PGN tags is a move
12389      * and not move number 1, retrieve it from the parser
12390      */
12391     if (cm != MoveNumberOne)
12392         LoadGameOneMove(cm);
12393
12394     /* load the remaining moves from the file */
12395     while (LoadGameOneMove(EndOfFile)) {
12396       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12397       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12398     }
12399
12400     /* rewind to the start of the game */
12401     currentMove = backwardMostMove;
12402
12403     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12404
12405     if (oldGameMode == AnalyzeFile ||
12406         oldGameMode == AnalyzeMode) {
12407       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12408       AnalyzeFileEvent();
12409     }
12410
12411     if(creatingBook) return TRUE;
12412     if (!matchMode && pos > 0) {
12413         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12414     } else
12415     if (matchMode || appData.timeDelay == 0) {
12416       ToEndEvent();
12417     } else if (appData.timeDelay > 0) {
12418       AutoPlayGameLoop();
12419     }
12420
12421     if (appData.debugMode)
12422         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12423
12424     loadFlag = 0; /* [HGM] true game starts */
12425     return TRUE;
12426 }
12427
12428 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12429 int
12430 ReloadPosition (int offset)
12431 {
12432     int positionNumber = lastLoadPositionNumber + offset;
12433     if (lastLoadPositionFP == NULL) {
12434         DisplayError(_("No position has been loaded yet"), 0);
12435         return FALSE;
12436     }
12437     if (positionNumber <= 0) {
12438         DisplayError(_("Can't back up any further"), 0);
12439         return FALSE;
12440     }
12441     return LoadPosition(lastLoadPositionFP, positionNumber,
12442                         lastLoadPositionTitle);
12443 }
12444
12445 /* Load the nth position from the given file */
12446 int
12447 LoadPositionFromFile (char *filename, int n, char *title)
12448 {
12449     FILE *f;
12450     char buf[MSG_SIZ];
12451
12452     if (strcmp(filename, "-") == 0) {
12453         return LoadPosition(stdin, n, "stdin");
12454     } else {
12455         f = fopen(filename, "rb");
12456         if (f == NULL) {
12457             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12458             DisplayError(buf, errno);
12459             return FALSE;
12460         } else {
12461             return LoadPosition(f, n, title);
12462         }
12463     }
12464 }
12465
12466 /* Load the nth position from the given open file, and close it */
12467 int
12468 LoadPosition (FILE *f, int positionNumber, char *title)
12469 {
12470     char *p, line[MSG_SIZ];
12471     Board initial_position;
12472     int i, j, fenMode, pn;
12473
12474     if (gameMode == Training )
12475         SetTrainingModeOff();
12476
12477     if (gameMode != BeginningOfGame) {
12478         Reset(FALSE, TRUE);
12479     }
12480     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12481         fclose(lastLoadPositionFP);
12482     }
12483     if (positionNumber == 0) positionNumber = 1;
12484     lastLoadPositionFP = f;
12485     lastLoadPositionNumber = positionNumber;
12486     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12487     if (first.pr == NoProc && !appData.noChessProgram) {
12488       StartChessProgram(&first);
12489       InitChessProgram(&first, FALSE);
12490     }
12491     pn = positionNumber;
12492     if (positionNumber < 0) {
12493         /* Negative position number means to seek to that byte offset */
12494         if (fseek(f, -positionNumber, 0) == -1) {
12495             DisplayError(_("Can't seek on position file"), 0);
12496             return FALSE;
12497         };
12498         pn = 1;
12499     } else {
12500         if (fseek(f, 0, 0) == -1) {
12501             if (f == lastLoadPositionFP ?
12502                 positionNumber == lastLoadPositionNumber + 1 :
12503                 positionNumber == 1) {
12504                 pn = 1;
12505             } else {
12506                 DisplayError(_("Can't seek on position file"), 0);
12507                 return FALSE;
12508             }
12509         }
12510     }
12511     /* See if this file is FEN or old-style xboard */
12512     if (fgets(line, MSG_SIZ, f) == NULL) {
12513         DisplayError(_("Position not found in file"), 0);
12514         return FALSE;
12515     }
12516     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12517     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12518
12519     if (pn >= 2) {
12520         if (fenMode || line[0] == '#') pn--;
12521         while (pn > 0) {
12522             /* skip positions before number pn */
12523             if (fgets(line, MSG_SIZ, f) == NULL) {
12524                 Reset(TRUE, TRUE);
12525                 DisplayError(_("Position not found in file"), 0);
12526                 return FALSE;
12527             }
12528             if (fenMode || line[0] == '#') pn--;
12529         }
12530     }
12531
12532     if (fenMode) {
12533         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12534             DisplayError(_("Bad FEN position in file"), 0);
12535             return FALSE;
12536         }
12537     } else {
12538         (void) fgets(line, MSG_SIZ, f);
12539         (void) fgets(line, MSG_SIZ, f);
12540
12541         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12542             (void) fgets(line, MSG_SIZ, f);
12543             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12544                 if (*p == ' ')
12545                   continue;
12546                 initial_position[i][j++] = CharToPiece(*p);
12547             }
12548         }
12549
12550         blackPlaysFirst = FALSE;
12551         if (!feof(f)) {
12552             (void) fgets(line, MSG_SIZ, f);
12553             if (strncmp(line, "black", strlen("black"))==0)
12554               blackPlaysFirst = TRUE;
12555         }
12556     }
12557     startedFromSetupPosition = TRUE;
12558
12559     CopyBoard(boards[0], initial_position);
12560     if (blackPlaysFirst) {
12561         currentMove = forwardMostMove = backwardMostMove = 1;
12562         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12563         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12564         CopyBoard(boards[1], initial_position);
12565         DisplayMessage("", _("Black to play"));
12566     } else {
12567         currentMove = forwardMostMove = backwardMostMove = 0;
12568         DisplayMessage("", _("White to play"));
12569     }
12570     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12571     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12572         SendToProgram("force\n", &first);
12573         SendBoard(&first, forwardMostMove);
12574     }
12575     if (appData.debugMode) {
12576 int i, j;
12577   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12578   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12579         fprintf(debugFP, "Load Position\n");
12580     }
12581
12582     if (positionNumber > 1) {
12583       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12584         DisplayTitle(line);
12585     } else {
12586         DisplayTitle(title);
12587     }
12588     gameMode = EditGame;
12589     ModeHighlight();
12590     ResetClocks();
12591     timeRemaining[0][1] = whiteTimeRemaining;
12592     timeRemaining[1][1] = blackTimeRemaining;
12593     DrawPosition(FALSE, boards[currentMove]);
12594
12595     return TRUE;
12596 }
12597
12598
12599 void
12600 CopyPlayerNameIntoFileName (char **dest, char *src)
12601 {
12602     while (*src != NULLCHAR && *src != ',') {
12603         if (*src == ' ') {
12604             *(*dest)++ = '_';
12605             src++;
12606         } else {
12607             *(*dest)++ = *src++;
12608         }
12609     }
12610 }
12611
12612 char *
12613 DefaultFileName (char *ext)
12614 {
12615     static char def[MSG_SIZ];
12616     char *p;
12617
12618     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12619         p = def;
12620         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12621         *p++ = '-';
12622         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12623         *p++ = '.';
12624         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12625     } else {
12626         def[0] = NULLCHAR;
12627     }
12628     return def;
12629 }
12630
12631 /* Save the current game to the given file */
12632 int
12633 SaveGameToFile (char *filename, int append)
12634 {
12635     FILE *f;
12636     char buf[MSG_SIZ];
12637     int result, i, t,tot=0;
12638
12639     if (strcmp(filename, "-") == 0) {
12640         return SaveGame(stdout, 0, NULL);
12641     } else {
12642         for(i=0; i<10; i++) { // upto 10 tries
12643              f = fopen(filename, append ? "a" : "w");
12644              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12645              if(f || errno != 13) break;
12646              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12647              tot += t;
12648         }
12649         if (f == NULL) {
12650             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12651             DisplayError(buf, errno);
12652             return FALSE;
12653         } else {
12654             safeStrCpy(buf, lastMsg, MSG_SIZ);
12655             DisplayMessage(_("Waiting for access to save file"), "");
12656             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12657             DisplayMessage(_("Saving game"), "");
12658             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12659             result = SaveGame(f, 0, NULL);
12660             DisplayMessage(buf, "");
12661             return result;
12662         }
12663     }
12664 }
12665
12666 char *
12667 SavePart (char *str)
12668 {
12669     static char buf[MSG_SIZ];
12670     char *p;
12671
12672     p = strchr(str, ' ');
12673     if (p == NULL) return str;
12674     strncpy(buf, str, p - str);
12675     buf[p - str] = NULLCHAR;
12676     return buf;
12677 }
12678
12679 #define PGN_MAX_LINE 75
12680
12681 #define PGN_SIDE_WHITE  0
12682 #define PGN_SIDE_BLACK  1
12683
12684 static int
12685 FindFirstMoveOutOfBook (int side)
12686 {
12687     int result = -1;
12688
12689     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12690         int index = backwardMostMove;
12691         int has_book_hit = 0;
12692
12693         if( (index % 2) != side ) {
12694             index++;
12695         }
12696
12697         while( index < forwardMostMove ) {
12698             /* Check to see if engine is in book */
12699             int depth = pvInfoList[index].depth;
12700             int score = pvInfoList[index].score;
12701             int in_book = 0;
12702
12703             if( depth <= 2 ) {
12704                 in_book = 1;
12705             }
12706             else if( score == 0 && depth == 63 ) {
12707                 in_book = 1; /* Zappa */
12708             }
12709             else if( score == 2 && depth == 99 ) {
12710                 in_book = 1; /* Abrok */
12711             }
12712
12713             has_book_hit += in_book;
12714
12715             if( ! in_book ) {
12716                 result = index;
12717
12718                 break;
12719             }
12720
12721             index += 2;
12722         }
12723     }
12724
12725     return result;
12726 }
12727
12728 void
12729 GetOutOfBookInfo (char * buf)
12730 {
12731     int oob[2];
12732     int i;
12733     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12734
12735     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12736     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12737
12738     *buf = '\0';
12739
12740     if( oob[0] >= 0 || oob[1] >= 0 ) {
12741         for( i=0; i<2; i++ ) {
12742             int idx = oob[i];
12743
12744             if( idx >= 0 ) {
12745                 if( i > 0 && oob[0] >= 0 ) {
12746                     strcat( buf, "   " );
12747                 }
12748
12749                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12750                 sprintf( buf+strlen(buf), "%s%.2f",
12751                     pvInfoList[idx].score >= 0 ? "+" : "",
12752                     pvInfoList[idx].score / 100.0 );
12753             }
12754         }
12755     }
12756 }
12757
12758 /* Save game in PGN style and close the file */
12759 int
12760 SaveGamePGN (FILE *f)
12761 {
12762     int i, offset, linelen, newblock;
12763 //    char *movetext;
12764     char numtext[32];
12765     int movelen, numlen, blank;
12766     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12767
12768     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12769
12770     PrintPGNTags(f, &gameInfo);
12771
12772     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12773
12774     if (backwardMostMove > 0 || startedFromSetupPosition) {
12775         char *fen = PositionToFEN(backwardMostMove, NULL);
12776         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12777         fprintf(f, "\n{--------------\n");
12778         PrintPosition(f, backwardMostMove);
12779         fprintf(f, "--------------}\n");
12780         free(fen);
12781     }
12782     else {
12783         /* [AS] Out of book annotation */
12784         if( appData.saveOutOfBookInfo ) {
12785             char buf[64];
12786
12787             GetOutOfBookInfo( buf );
12788
12789             if( buf[0] != '\0' ) {
12790                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12791             }
12792         }
12793
12794         fprintf(f, "\n");
12795     }
12796
12797     i = backwardMostMove;
12798     linelen = 0;
12799     newblock = TRUE;
12800
12801     while (i < forwardMostMove) {
12802         /* Print comments preceding this move */
12803         if (commentList[i] != NULL) {
12804             if (linelen > 0) fprintf(f, "\n");
12805             fprintf(f, "%s", commentList[i]);
12806             linelen = 0;
12807             newblock = TRUE;
12808         }
12809
12810         /* Format move number */
12811         if ((i % 2) == 0)
12812           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12813         else
12814           if (newblock)
12815             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12816           else
12817             numtext[0] = NULLCHAR;
12818
12819         numlen = strlen(numtext);
12820         newblock = FALSE;
12821
12822         /* Print move number */
12823         blank = linelen > 0 && numlen > 0;
12824         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12825             fprintf(f, "\n");
12826             linelen = 0;
12827             blank = 0;
12828         }
12829         if (blank) {
12830             fprintf(f, " ");
12831             linelen++;
12832         }
12833         fprintf(f, "%s", numtext);
12834         linelen += numlen;
12835
12836         /* Get move */
12837         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12838         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12839
12840         /* Print move */
12841         blank = linelen > 0 && movelen > 0;
12842         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12843             fprintf(f, "\n");
12844             linelen = 0;
12845             blank = 0;
12846         }
12847         if (blank) {
12848             fprintf(f, " ");
12849             linelen++;
12850         }
12851         fprintf(f, "%s", move_buffer);
12852         linelen += movelen;
12853
12854         /* [AS] Add PV info if present */
12855         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12856             /* [HGM] add time */
12857             char buf[MSG_SIZ]; int seconds;
12858
12859             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12860
12861             if( seconds <= 0)
12862               buf[0] = 0;
12863             else
12864               if( seconds < 30 )
12865                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12866               else
12867                 {
12868                   seconds = (seconds + 4)/10; // round to full seconds
12869                   if( seconds < 60 )
12870                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12871                   else
12872                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12873                 }
12874
12875             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12876                       pvInfoList[i].score >= 0 ? "+" : "",
12877                       pvInfoList[i].score / 100.0,
12878                       pvInfoList[i].depth,
12879                       buf );
12880
12881             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12882
12883             /* Print score/depth */
12884             blank = linelen > 0 && movelen > 0;
12885             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12886                 fprintf(f, "\n");
12887                 linelen = 0;
12888                 blank = 0;
12889             }
12890             if (blank) {
12891                 fprintf(f, " ");
12892                 linelen++;
12893             }
12894             fprintf(f, "%s", move_buffer);
12895             linelen += movelen;
12896         }
12897
12898         i++;
12899     }
12900
12901     /* Start a new line */
12902     if (linelen > 0) fprintf(f, "\n");
12903
12904     /* Print comments after last move */
12905     if (commentList[i] != NULL) {
12906         fprintf(f, "%s\n", commentList[i]);
12907     }
12908
12909     /* Print result */
12910     if (gameInfo.resultDetails != NULL &&
12911         gameInfo.resultDetails[0] != NULLCHAR) {
12912         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12913                 PGNResult(gameInfo.result));
12914     } else {
12915         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12916     }
12917
12918     fclose(f);
12919     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12920     return TRUE;
12921 }
12922
12923 /* Save game in old style and close the file */
12924 int
12925 SaveGameOldStyle (FILE *f)
12926 {
12927     int i, offset;
12928     time_t tm;
12929
12930     tm = time((time_t *) NULL);
12931
12932     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12933     PrintOpponents(f);
12934
12935     if (backwardMostMove > 0 || startedFromSetupPosition) {
12936         fprintf(f, "\n[--------------\n");
12937         PrintPosition(f, backwardMostMove);
12938         fprintf(f, "--------------]\n");
12939     } else {
12940         fprintf(f, "\n");
12941     }
12942
12943     i = backwardMostMove;
12944     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12945
12946     while (i < forwardMostMove) {
12947         if (commentList[i] != NULL) {
12948             fprintf(f, "[%s]\n", commentList[i]);
12949         }
12950
12951         if ((i % 2) == 1) {
12952             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12953             i++;
12954         } else {
12955             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12956             i++;
12957             if (commentList[i] != NULL) {
12958                 fprintf(f, "\n");
12959                 continue;
12960             }
12961             if (i >= forwardMostMove) {
12962                 fprintf(f, "\n");
12963                 break;
12964             }
12965             fprintf(f, "%s\n", parseList[i]);
12966             i++;
12967         }
12968     }
12969
12970     if (commentList[i] != NULL) {
12971         fprintf(f, "[%s]\n", commentList[i]);
12972     }
12973
12974     /* This isn't really the old style, but it's close enough */
12975     if (gameInfo.resultDetails != NULL &&
12976         gameInfo.resultDetails[0] != NULLCHAR) {
12977         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12978                 gameInfo.resultDetails);
12979     } else {
12980         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12981     }
12982
12983     fclose(f);
12984     return TRUE;
12985 }
12986
12987 /* Save the current game to open file f and close the file */
12988 int
12989 SaveGame (FILE *f, int dummy, char *dummy2)
12990 {
12991     if (gameMode == EditPosition) EditPositionDone(TRUE);
12992     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12993     if (appData.oldSaveStyle)
12994       return SaveGameOldStyle(f);
12995     else
12996       return SaveGamePGN(f);
12997 }
12998
12999 /* Save the current position to the given file */
13000 int
13001 SavePositionToFile (char *filename)
13002 {
13003     FILE *f;
13004     char buf[MSG_SIZ];
13005
13006     if (strcmp(filename, "-") == 0) {
13007         return SavePosition(stdout, 0, NULL);
13008     } else {
13009         f = fopen(filename, "a");
13010         if (f == NULL) {
13011             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13012             DisplayError(buf, errno);
13013             return FALSE;
13014         } else {
13015             safeStrCpy(buf, lastMsg, MSG_SIZ);
13016             DisplayMessage(_("Waiting for access to save file"), "");
13017             flock(fileno(f), LOCK_EX); // [HGM] lock
13018             DisplayMessage(_("Saving position"), "");
13019             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13020             SavePosition(f, 0, NULL);
13021             DisplayMessage(buf, "");
13022             return TRUE;
13023         }
13024     }
13025 }
13026
13027 /* Save the current position to the given open file and close the file */
13028 int
13029 SavePosition (FILE *f, int dummy, char *dummy2)
13030 {
13031     time_t tm;
13032     char *fen;
13033
13034     if (gameMode == EditPosition) EditPositionDone(TRUE);
13035     if (appData.oldSaveStyle) {
13036         tm = time((time_t *) NULL);
13037
13038         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13039         PrintOpponents(f);
13040         fprintf(f, "[--------------\n");
13041         PrintPosition(f, currentMove);
13042         fprintf(f, "--------------]\n");
13043     } else {
13044         fen = PositionToFEN(currentMove, NULL);
13045         fprintf(f, "%s\n", fen);
13046         free(fen);
13047     }
13048     fclose(f);
13049     return TRUE;
13050 }
13051
13052 void
13053 ReloadCmailMsgEvent (int unregister)
13054 {
13055 #if !WIN32
13056     static char *inFilename = NULL;
13057     static char *outFilename;
13058     int i;
13059     struct stat inbuf, outbuf;
13060     int status;
13061
13062     /* Any registered moves are unregistered if unregister is set, */
13063     /* i.e. invoked by the signal handler */
13064     if (unregister) {
13065         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13066             cmailMoveRegistered[i] = FALSE;
13067             if (cmailCommentList[i] != NULL) {
13068                 free(cmailCommentList[i]);
13069                 cmailCommentList[i] = NULL;
13070             }
13071         }
13072         nCmailMovesRegistered = 0;
13073     }
13074
13075     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13076         cmailResult[i] = CMAIL_NOT_RESULT;
13077     }
13078     nCmailResults = 0;
13079
13080     if (inFilename == NULL) {
13081         /* Because the filenames are static they only get malloced once  */
13082         /* and they never get freed                                      */
13083         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13084         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13085
13086         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13087         sprintf(outFilename, "%s.out", appData.cmailGameName);
13088     }
13089
13090     status = stat(outFilename, &outbuf);
13091     if (status < 0) {
13092         cmailMailedMove = FALSE;
13093     } else {
13094         status = stat(inFilename, &inbuf);
13095         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13096     }
13097
13098     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13099        counts the games, notes how each one terminated, etc.
13100
13101        It would be nice to remove this kludge and instead gather all
13102        the information while building the game list.  (And to keep it
13103        in the game list nodes instead of having a bunch of fixed-size
13104        parallel arrays.)  Note this will require getting each game's
13105        termination from the PGN tags, as the game list builder does
13106        not process the game moves.  --mann
13107        */
13108     cmailMsgLoaded = TRUE;
13109     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13110
13111     /* Load first game in the file or popup game menu */
13112     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13113
13114 #endif /* !WIN32 */
13115     return;
13116 }
13117
13118 int
13119 RegisterMove ()
13120 {
13121     FILE *f;
13122     char string[MSG_SIZ];
13123
13124     if (   cmailMailedMove
13125         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13126         return TRUE;            /* Allow free viewing  */
13127     }
13128
13129     /* Unregister move to ensure that we don't leave RegisterMove        */
13130     /* with the move registered when the conditions for registering no   */
13131     /* longer hold                                                       */
13132     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13133         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13134         nCmailMovesRegistered --;
13135
13136         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13137           {
13138               free(cmailCommentList[lastLoadGameNumber - 1]);
13139               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13140           }
13141     }
13142
13143     if (cmailOldMove == -1) {
13144         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13145         return FALSE;
13146     }
13147
13148     if (currentMove > cmailOldMove + 1) {
13149         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13150         return FALSE;
13151     }
13152
13153     if (currentMove < cmailOldMove) {
13154         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13155         return FALSE;
13156     }
13157
13158     if (forwardMostMove > currentMove) {
13159         /* Silently truncate extra moves */
13160         TruncateGame();
13161     }
13162
13163     if (   (currentMove == cmailOldMove + 1)
13164         || (   (currentMove == cmailOldMove)
13165             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13166                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13167         if (gameInfo.result != GameUnfinished) {
13168             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13169         }
13170
13171         if (commentList[currentMove] != NULL) {
13172             cmailCommentList[lastLoadGameNumber - 1]
13173               = StrSave(commentList[currentMove]);
13174         }
13175         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13176
13177         if (appData.debugMode)
13178           fprintf(debugFP, "Saving %s for game %d\n",
13179                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13180
13181         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13182
13183         f = fopen(string, "w");
13184         if (appData.oldSaveStyle) {
13185             SaveGameOldStyle(f); /* also closes the file */
13186
13187             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13188             f = fopen(string, "w");
13189             SavePosition(f, 0, NULL); /* also closes the file */
13190         } else {
13191             fprintf(f, "{--------------\n");
13192             PrintPosition(f, currentMove);
13193             fprintf(f, "--------------}\n\n");
13194
13195             SaveGame(f, 0, NULL); /* also closes the file*/
13196         }
13197
13198         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13199         nCmailMovesRegistered ++;
13200     } else if (nCmailGames == 1) {
13201         DisplayError(_("You have not made a move yet"), 0);
13202         return FALSE;
13203     }
13204
13205     return TRUE;
13206 }
13207
13208 void
13209 MailMoveEvent ()
13210 {
13211 #if !WIN32
13212     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13213     FILE *commandOutput;
13214     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13215     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13216     int nBuffers;
13217     int i;
13218     int archived;
13219     char *arcDir;
13220
13221     if (! cmailMsgLoaded) {
13222         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13223         return;
13224     }
13225
13226     if (nCmailGames == nCmailResults) {
13227         DisplayError(_("No unfinished games"), 0);
13228         return;
13229     }
13230
13231 #if CMAIL_PROHIBIT_REMAIL
13232     if (cmailMailedMove) {
13233       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);
13234         DisplayError(msg, 0);
13235         return;
13236     }
13237 #endif
13238
13239     if (! (cmailMailedMove || RegisterMove())) return;
13240
13241     if (   cmailMailedMove
13242         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13243       snprintf(string, MSG_SIZ, partCommandString,
13244                appData.debugMode ? " -v" : "", appData.cmailGameName);
13245         commandOutput = popen(string, "r");
13246
13247         if (commandOutput == NULL) {
13248             DisplayError(_("Failed to invoke cmail"), 0);
13249         } else {
13250             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13251                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13252             }
13253             if (nBuffers > 1) {
13254                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13255                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13256                 nBytes = MSG_SIZ - 1;
13257             } else {
13258                 (void) memcpy(msg, buffer, nBytes);
13259             }
13260             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13261
13262             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13263                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13264
13265                 archived = TRUE;
13266                 for (i = 0; i < nCmailGames; i ++) {
13267                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13268                         archived = FALSE;
13269                     }
13270                 }
13271                 if (   archived
13272                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13273                         != NULL)) {
13274                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13275                            arcDir,
13276                            appData.cmailGameName,
13277                            gameInfo.date);
13278                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13279                     cmailMsgLoaded = FALSE;
13280                 }
13281             }
13282
13283             DisplayInformation(msg);
13284             pclose(commandOutput);
13285         }
13286     } else {
13287         if ((*cmailMsg) != '\0') {
13288             DisplayInformation(cmailMsg);
13289         }
13290     }
13291
13292     return;
13293 #endif /* !WIN32 */
13294 }
13295
13296 char *
13297 CmailMsg ()
13298 {
13299 #if WIN32
13300     return NULL;
13301 #else
13302     int  prependComma = 0;
13303     char number[5];
13304     char string[MSG_SIZ];       /* Space for game-list */
13305     int  i;
13306
13307     if (!cmailMsgLoaded) return "";
13308
13309     if (cmailMailedMove) {
13310       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13311     } else {
13312         /* Create a list of games left */
13313       snprintf(string, MSG_SIZ, "[");
13314         for (i = 0; i < nCmailGames; i ++) {
13315             if (! (   cmailMoveRegistered[i]
13316                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13317                 if (prependComma) {
13318                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13319                 } else {
13320                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13321                     prependComma = 1;
13322                 }
13323
13324                 strcat(string, number);
13325             }
13326         }
13327         strcat(string, "]");
13328
13329         if (nCmailMovesRegistered + nCmailResults == 0) {
13330             switch (nCmailGames) {
13331               case 1:
13332                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13333                 break;
13334
13335               case 2:
13336                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13337                 break;
13338
13339               default:
13340                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13341                          nCmailGames);
13342                 break;
13343             }
13344         } else {
13345             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13346               case 1:
13347                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13348                          string);
13349                 break;
13350
13351               case 0:
13352                 if (nCmailResults == nCmailGames) {
13353                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13354                 } else {
13355                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13356                 }
13357                 break;
13358
13359               default:
13360                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13361                          string);
13362             }
13363         }
13364     }
13365     return cmailMsg;
13366 #endif /* WIN32 */
13367 }
13368
13369 void
13370 ResetGameEvent ()
13371 {
13372     if (gameMode == Training)
13373       SetTrainingModeOff();
13374
13375     Reset(TRUE, TRUE);
13376     cmailMsgLoaded = FALSE;
13377     if (appData.icsActive) {
13378       SendToICS(ics_prefix);
13379       SendToICS("refresh\n");
13380     }
13381 }
13382
13383 void
13384 ExitEvent (int status)
13385 {
13386     exiting++;
13387     if (exiting > 2) {
13388       /* Give up on clean exit */
13389       exit(status);
13390     }
13391     if (exiting > 1) {
13392       /* Keep trying for clean exit */
13393       return;
13394     }
13395
13396     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13397
13398     if (telnetISR != NULL) {
13399       RemoveInputSource(telnetISR);
13400     }
13401     if (icsPR != NoProc) {
13402       DestroyChildProcess(icsPR, TRUE);
13403     }
13404
13405     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13406     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13407
13408     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13409     /* make sure this other one finishes before killing it!                  */
13410     if(endingGame) { int count = 0;
13411         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13412         while(endingGame && count++ < 10) DoSleep(1);
13413         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13414     }
13415
13416     /* Kill off chess programs */
13417     if (first.pr != NoProc) {
13418         ExitAnalyzeMode();
13419
13420         DoSleep( appData.delayBeforeQuit );
13421         SendToProgram("quit\n", &first);
13422         DoSleep( appData.delayAfterQuit );
13423         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13424     }
13425     if (second.pr != NoProc) {
13426         DoSleep( appData.delayBeforeQuit );
13427         SendToProgram("quit\n", &second);
13428         DoSleep( appData.delayAfterQuit );
13429         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13430     }
13431     if (first.isr != NULL) {
13432         RemoveInputSource(first.isr);
13433     }
13434     if (second.isr != NULL) {
13435         RemoveInputSource(second.isr);
13436     }
13437
13438     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13439     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13440
13441     ShutDownFrontEnd();
13442     exit(status);
13443 }
13444
13445 void
13446 PauseEngine (ChessProgramState *cps)
13447 {
13448     SendToProgram("pause\n", cps);
13449     cps->pause = 2;
13450 }
13451
13452 void
13453 UnPauseEngine (ChessProgramState *cps)
13454 {
13455     SendToProgram("resume\n", cps);
13456     cps->pause = 1;
13457 }
13458
13459 void
13460 PauseEvent ()
13461 {
13462     if (appData.debugMode)
13463         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13464     if (pausing) {
13465         pausing = FALSE;
13466         ModeHighlight();
13467         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13468             StartClocks();
13469             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13470                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13471                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13472             }
13473             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13474             HandleMachineMove(stashedInputMove, stalledEngine);
13475             stalledEngine = NULL;
13476             return;
13477         }
13478         if (gameMode == MachinePlaysWhite ||
13479             gameMode == TwoMachinesPlay   ||
13480             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13481             if(first.pause)  UnPauseEngine(&first);
13482             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13483             if(second.pause) UnPauseEngine(&second);
13484             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13485             StartClocks();
13486         } else {
13487             DisplayBothClocks();
13488         }
13489         if (gameMode == PlayFromGameFile) {
13490             if (appData.timeDelay >= 0)
13491                 AutoPlayGameLoop();
13492         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13493             Reset(FALSE, TRUE);
13494             SendToICS(ics_prefix);
13495             SendToICS("refresh\n");
13496         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13497             ForwardInner(forwardMostMove);
13498         }
13499         pauseExamInvalid = FALSE;
13500     } else {
13501         switch (gameMode) {
13502           default:
13503             return;
13504           case IcsExamining:
13505             pauseExamForwardMostMove = forwardMostMove;
13506             pauseExamInvalid = FALSE;
13507             /* fall through */
13508           case IcsObserving:
13509           case IcsPlayingWhite:
13510           case IcsPlayingBlack:
13511             pausing = TRUE;
13512             ModeHighlight();
13513             return;
13514           case PlayFromGameFile:
13515             (void) StopLoadGameTimer();
13516             pausing = TRUE;
13517             ModeHighlight();
13518             break;
13519           case BeginningOfGame:
13520             if (appData.icsActive) return;
13521             /* else fall through */
13522           case MachinePlaysWhite:
13523           case MachinePlaysBlack:
13524           case TwoMachinesPlay:
13525             if (forwardMostMove == 0)
13526               return;           /* don't pause if no one has moved */
13527             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13528                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13529                 if(onMove->pause) {           // thinking engine can be paused
13530                     PauseEngine(onMove);      // do it
13531                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13532                         PauseEngine(onMove->other);
13533                     else
13534                         SendToProgram("easy\n", onMove->other);
13535                     StopClocks();
13536                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13537             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13538                 if(first.pause) {
13539                     PauseEngine(&first);
13540                     StopClocks();
13541                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13542             } else { // human on move, pause pondering by either method
13543                 if(first.pause)
13544                     PauseEngine(&first);
13545                 else if(appData.ponderNextMove)
13546                     SendToProgram("easy\n", &first);
13547                 StopClocks();
13548             }
13549             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13550           case AnalyzeMode:
13551             pausing = TRUE;
13552             ModeHighlight();
13553             break;
13554         }
13555     }
13556 }
13557
13558 void
13559 EditCommentEvent ()
13560 {
13561     char title[MSG_SIZ];
13562
13563     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13564       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13565     } else {
13566       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13567                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13568                parseList[currentMove - 1]);
13569     }
13570
13571     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13572 }
13573
13574
13575 void
13576 EditTagsEvent ()
13577 {
13578     char *tags = PGNTags(&gameInfo);
13579     bookUp = FALSE;
13580     EditTagsPopUp(tags, NULL);
13581     free(tags);
13582 }
13583
13584 void
13585 ToggleSecond ()
13586 {
13587   if(second.analyzing) {
13588     SendToProgram("exit\n", &second);
13589     second.analyzing = FALSE;
13590   } else {
13591     if (second.pr == NoProc) StartChessProgram(&second);
13592     InitChessProgram(&second, FALSE);
13593     FeedMovesToProgram(&second, currentMove);
13594
13595     SendToProgram("analyze\n", &second);
13596     second.analyzing = TRUE;
13597   }
13598 }
13599
13600 /* Toggle ShowThinking */
13601 void
13602 ToggleShowThinking()
13603 {
13604   appData.showThinking = !appData.showThinking;
13605   ShowThinkingEvent();
13606 }
13607
13608 int
13609 AnalyzeModeEvent ()
13610 {
13611     char buf[MSG_SIZ];
13612
13613     if (!first.analysisSupport) {
13614       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13615       DisplayError(buf, 0);
13616       return 0;
13617     }
13618     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13619     if (appData.icsActive) {
13620         if (gameMode != IcsObserving) {
13621           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13622             DisplayError(buf, 0);
13623             /* secure check */
13624             if (appData.icsEngineAnalyze) {
13625                 if (appData.debugMode)
13626                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13627                 ExitAnalyzeMode();
13628                 ModeHighlight();
13629             }
13630             return 0;
13631         }
13632         /* if enable, user wants to disable icsEngineAnalyze */
13633         if (appData.icsEngineAnalyze) {
13634                 ExitAnalyzeMode();
13635                 ModeHighlight();
13636                 return 0;
13637         }
13638         appData.icsEngineAnalyze = TRUE;
13639         if (appData.debugMode)
13640             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13641     }
13642
13643     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13644     if (appData.noChessProgram || gameMode == AnalyzeMode)
13645       return 0;
13646
13647     if (gameMode != AnalyzeFile) {
13648         if (!appData.icsEngineAnalyze) {
13649                EditGameEvent();
13650                if (gameMode != EditGame) return 0;
13651         }
13652         if (!appData.showThinking) ToggleShowThinking();
13653         ResurrectChessProgram();
13654         SendToProgram("analyze\n", &first);
13655         first.analyzing = TRUE;
13656         /*first.maybeThinking = TRUE;*/
13657         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13658         EngineOutputPopUp();
13659     }
13660     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13661     pausing = FALSE;
13662     ModeHighlight();
13663     SetGameInfo();
13664
13665     StartAnalysisClock();
13666     GetTimeMark(&lastNodeCountTime);
13667     lastNodeCount = 0;
13668     return 1;
13669 }
13670
13671 void
13672 AnalyzeFileEvent ()
13673 {
13674     if (appData.noChessProgram || gameMode == AnalyzeFile)
13675       return;
13676
13677     if (!first.analysisSupport) {
13678       char buf[MSG_SIZ];
13679       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13680       DisplayError(buf, 0);
13681       return;
13682     }
13683
13684     if (gameMode != AnalyzeMode) {
13685         keepInfo = 1; // mere annotating should not alter PGN tags
13686         EditGameEvent();
13687         keepInfo = 0;
13688         if (gameMode != EditGame) return;
13689         if (!appData.showThinking) ToggleShowThinking();
13690         ResurrectChessProgram();
13691         SendToProgram("analyze\n", &first);
13692         first.analyzing = TRUE;
13693         /*first.maybeThinking = TRUE;*/
13694         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13695         EngineOutputPopUp();
13696     }
13697     gameMode = AnalyzeFile;
13698     pausing = FALSE;
13699     ModeHighlight();
13700
13701     StartAnalysisClock();
13702     GetTimeMark(&lastNodeCountTime);
13703     lastNodeCount = 0;
13704     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13705     AnalysisPeriodicEvent(1);
13706 }
13707
13708 void
13709 MachineWhiteEvent ()
13710 {
13711     char buf[MSG_SIZ];
13712     char *bookHit = NULL;
13713
13714     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13715       return;
13716
13717
13718     if (gameMode == PlayFromGameFile ||
13719         gameMode == TwoMachinesPlay  ||
13720         gameMode == Training         ||
13721         gameMode == AnalyzeMode      ||
13722         gameMode == EndOfGame)
13723         EditGameEvent();
13724
13725     if (gameMode == EditPosition)
13726         EditPositionDone(TRUE);
13727
13728     if (!WhiteOnMove(currentMove)) {
13729         DisplayError(_("It is not White's turn"), 0);
13730         return;
13731     }
13732
13733     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13734       ExitAnalyzeMode();
13735
13736     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13737         gameMode == AnalyzeFile)
13738         TruncateGame();
13739
13740     ResurrectChessProgram();    /* in case it isn't running */
13741     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13742         gameMode = MachinePlaysWhite;
13743         ResetClocks();
13744     } else
13745     gameMode = MachinePlaysWhite;
13746     pausing = FALSE;
13747     ModeHighlight();
13748     SetGameInfo();
13749     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13750     DisplayTitle(buf);
13751     if (first.sendName) {
13752       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13753       SendToProgram(buf, &first);
13754     }
13755     if (first.sendTime) {
13756       if (first.useColors) {
13757         SendToProgram("black\n", &first); /*gnu kludge*/
13758       }
13759       SendTimeRemaining(&first, TRUE);
13760     }
13761     if (first.useColors) {
13762       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13763     }
13764     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13765     SetMachineThinkingEnables();
13766     first.maybeThinking = TRUE;
13767     StartClocks();
13768     firstMove = FALSE;
13769
13770     if (appData.autoFlipView && !flipView) {
13771       flipView = !flipView;
13772       DrawPosition(FALSE, NULL);
13773       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13774     }
13775
13776     if(bookHit) { // [HGM] book: simulate book reply
13777         static char bookMove[MSG_SIZ]; // a bit generous?
13778
13779         programStats.nodes = programStats.depth = programStats.time =
13780         programStats.score = programStats.got_only_move = 0;
13781         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13782
13783         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13784         strcat(bookMove, bookHit);
13785         HandleMachineMove(bookMove, &first);
13786     }
13787 }
13788
13789 void
13790 MachineBlackEvent ()
13791 {
13792   char buf[MSG_SIZ];
13793   char *bookHit = NULL;
13794
13795     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13796         return;
13797
13798
13799     if (gameMode == PlayFromGameFile ||
13800         gameMode == TwoMachinesPlay  ||
13801         gameMode == Training         ||
13802         gameMode == AnalyzeMode      ||
13803         gameMode == EndOfGame)
13804         EditGameEvent();
13805
13806     if (gameMode == EditPosition)
13807         EditPositionDone(TRUE);
13808
13809     if (WhiteOnMove(currentMove)) {
13810         DisplayError(_("It is not Black's turn"), 0);
13811         return;
13812     }
13813
13814     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13815       ExitAnalyzeMode();
13816
13817     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13818         gameMode == AnalyzeFile)
13819         TruncateGame();
13820
13821     ResurrectChessProgram();    /* in case it isn't running */
13822     gameMode = MachinePlaysBlack;
13823     pausing = FALSE;
13824     ModeHighlight();
13825     SetGameInfo();
13826     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13827     DisplayTitle(buf);
13828     if (first.sendName) {
13829       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13830       SendToProgram(buf, &first);
13831     }
13832     if (first.sendTime) {
13833       if (first.useColors) {
13834         SendToProgram("white\n", &first); /*gnu kludge*/
13835       }
13836       SendTimeRemaining(&first, FALSE);
13837     }
13838     if (first.useColors) {
13839       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13840     }
13841     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13842     SetMachineThinkingEnables();
13843     first.maybeThinking = TRUE;
13844     StartClocks();
13845
13846     if (appData.autoFlipView && flipView) {
13847       flipView = !flipView;
13848       DrawPosition(FALSE, NULL);
13849       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13850     }
13851     if(bookHit) { // [HGM] book: simulate book reply
13852         static char bookMove[MSG_SIZ]; // a bit generous?
13853
13854         programStats.nodes = programStats.depth = programStats.time =
13855         programStats.score = programStats.got_only_move = 0;
13856         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13857
13858         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13859         strcat(bookMove, bookHit);
13860         HandleMachineMove(bookMove, &first);
13861     }
13862 }
13863
13864
13865 void
13866 DisplayTwoMachinesTitle ()
13867 {
13868     char buf[MSG_SIZ];
13869     if (appData.matchGames > 0) {
13870         if(appData.tourneyFile[0]) {
13871           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13872                    gameInfo.white, _("vs."), gameInfo.black,
13873                    nextGame+1, appData.matchGames+1,
13874                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13875         } else
13876         if (first.twoMachinesColor[0] == 'w') {
13877           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13878                    gameInfo.white, _("vs."),  gameInfo.black,
13879                    first.matchWins, second.matchWins,
13880                    matchGame - 1 - (first.matchWins + second.matchWins));
13881         } else {
13882           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13883                    gameInfo.white, _("vs."), gameInfo.black,
13884                    second.matchWins, first.matchWins,
13885                    matchGame - 1 - (first.matchWins + second.matchWins));
13886         }
13887     } else {
13888       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13889     }
13890     DisplayTitle(buf);
13891 }
13892
13893 void
13894 SettingsMenuIfReady ()
13895 {
13896   if (second.lastPing != second.lastPong) {
13897     DisplayMessage("", _("Waiting for second chess program"));
13898     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13899     return;
13900   }
13901   ThawUI();
13902   DisplayMessage("", "");
13903   SettingsPopUp(&second);
13904 }
13905
13906 int
13907 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13908 {
13909     char buf[MSG_SIZ];
13910     if (cps->pr == NoProc) {
13911         StartChessProgram(cps);
13912         if (cps->protocolVersion == 1) {
13913           retry();
13914         } else {
13915           /* kludge: allow timeout for initial "feature" command */
13916           FreezeUI();
13917           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13918           DisplayMessage("", buf);
13919           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13920         }
13921         return 1;
13922     }
13923     return 0;
13924 }
13925
13926 void
13927 TwoMachinesEvent P((void))
13928 {
13929     int i;
13930     char buf[MSG_SIZ];
13931     ChessProgramState *onmove;
13932     char *bookHit = NULL;
13933     static int stalling = 0;
13934     TimeMark now;
13935     long wait;
13936
13937     if (appData.noChessProgram) return;
13938
13939     switch (gameMode) {
13940       case TwoMachinesPlay:
13941         return;
13942       case MachinePlaysWhite:
13943       case MachinePlaysBlack:
13944         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13945             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13946             return;
13947         }
13948         /* fall through */
13949       case BeginningOfGame:
13950       case PlayFromGameFile:
13951       case EndOfGame:
13952         EditGameEvent();
13953         if (gameMode != EditGame) return;
13954         break;
13955       case EditPosition:
13956         EditPositionDone(TRUE);
13957         break;
13958       case AnalyzeMode:
13959       case AnalyzeFile:
13960         ExitAnalyzeMode();
13961         break;
13962       case EditGame:
13963       default:
13964         break;
13965     }
13966
13967 //    forwardMostMove = currentMove;
13968     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13969
13970     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13971
13972     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13973     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13974       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13975       return;
13976     }
13977
13978     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13979         DisplayError("second engine does not play this", 0);
13980         return;
13981     }
13982
13983     if(!stalling) {
13984       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13985       SendToProgram("force\n", &second);
13986       stalling = 1;
13987       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13988       return;
13989     }
13990     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13991     if(appData.matchPause>10000 || appData.matchPause<10)
13992                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13993     wait = SubtractTimeMarks(&now, &pauseStart);
13994     if(wait < appData.matchPause) {
13995         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13996         return;
13997     }
13998     // we are now committed to starting the game
13999     stalling = 0;
14000     DisplayMessage("", "");
14001     if (startedFromSetupPosition) {
14002         SendBoard(&second, backwardMostMove);
14003     if (appData.debugMode) {
14004         fprintf(debugFP, "Two Machines\n");
14005     }
14006     }
14007     for (i = backwardMostMove; i < forwardMostMove; i++) {
14008         SendMoveToProgram(i, &second);
14009     }
14010
14011     gameMode = TwoMachinesPlay;
14012     pausing = FALSE;
14013     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14014     SetGameInfo();
14015     DisplayTwoMachinesTitle();
14016     firstMove = TRUE;
14017     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14018         onmove = &first;
14019     } else {
14020         onmove = &second;
14021     }
14022     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14023     SendToProgram(first.computerString, &first);
14024     if (first.sendName) {
14025       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14026       SendToProgram(buf, &first);
14027     }
14028     SendToProgram(second.computerString, &second);
14029     if (second.sendName) {
14030       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14031       SendToProgram(buf, &second);
14032     }
14033
14034     ResetClocks();
14035     if (!first.sendTime || !second.sendTime) {
14036         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14037         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14038     }
14039     if (onmove->sendTime) {
14040       if (onmove->useColors) {
14041         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14042       }
14043       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14044     }
14045     if (onmove->useColors) {
14046       SendToProgram(onmove->twoMachinesColor, onmove);
14047     }
14048     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14049 //    SendToProgram("go\n", onmove);
14050     onmove->maybeThinking = TRUE;
14051     SetMachineThinkingEnables();
14052
14053     StartClocks();
14054
14055     if(bookHit) { // [HGM] book: simulate book reply
14056         static char bookMove[MSG_SIZ]; // a bit generous?
14057
14058         programStats.nodes = programStats.depth = programStats.time =
14059         programStats.score = programStats.got_only_move = 0;
14060         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14061
14062         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14063         strcat(bookMove, bookHit);
14064         savedMessage = bookMove; // args for deferred call
14065         savedState = onmove;
14066         ScheduleDelayedEvent(DeferredBookMove, 1);
14067     }
14068 }
14069
14070 void
14071 TrainingEvent ()
14072 {
14073     if (gameMode == Training) {
14074       SetTrainingModeOff();
14075       gameMode = PlayFromGameFile;
14076       DisplayMessage("", _("Training mode off"));
14077     } else {
14078       gameMode = Training;
14079       animateTraining = appData.animate;
14080
14081       /* make sure we are not already at the end of the game */
14082       if (currentMove < forwardMostMove) {
14083         SetTrainingModeOn();
14084         DisplayMessage("", _("Training mode on"));
14085       } else {
14086         gameMode = PlayFromGameFile;
14087         DisplayError(_("Already at end of game"), 0);
14088       }
14089     }
14090     ModeHighlight();
14091 }
14092
14093 void
14094 IcsClientEvent ()
14095 {
14096     if (!appData.icsActive) return;
14097     switch (gameMode) {
14098       case IcsPlayingWhite:
14099       case IcsPlayingBlack:
14100       case IcsObserving:
14101       case IcsIdle:
14102       case BeginningOfGame:
14103       case IcsExamining:
14104         return;
14105
14106       case EditGame:
14107         break;
14108
14109       case EditPosition:
14110         EditPositionDone(TRUE);
14111         break;
14112
14113       case AnalyzeMode:
14114       case AnalyzeFile:
14115         ExitAnalyzeMode();
14116         break;
14117
14118       default:
14119         EditGameEvent();
14120         break;
14121     }
14122
14123     gameMode = IcsIdle;
14124     ModeHighlight();
14125     return;
14126 }
14127
14128 void
14129 EditGameEvent ()
14130 {
14131     int i;
14132
14133     switch (gameMode) {
14134       case Training:
14135         SetTrainingModeOff();
14136         break;
14137       case MachinePlaysWhite:
14138       case MachinePlaysBlack:
14139       case BeginningOfGame:
14140         SendToProgram("force\n", &first);
14141         SetUserThinkingEnables();
14142         break;
14143       case PlayFromGameFile:
14144         (void) StopLoadGameTimer();
14145         if (gameFileFP != NULL) {
14146             gameFileFP = NULL;
14147         }
14148         break;
14149       case EditPosition:
14150         EditPositionDone(TRUE);
14151         break;
14152       case AnalyzeMode:
14153       case AnalyzeFile:
14154         ExitAnalyzeMode();
14155         SendToProgram("force\n", &first);
14156         break;
14157       case TwoMachinesPlay:
14158         GameEnds(EndOfFile, NULL, GE_PLAYER);
14159         ResurrectChessProgram();
14160         SetUserThinkingEnables();
14161         break;
14162       case EndOfGame:
14163         ResurrectChessProgram();
14164         break;
14165       case IcsPlayingBlack:
14166       case IcsPlayingWhite:
14167         DisplayError(_("Warning: You are still playing a game"), 0);
14168         break;
14169       case IcsObserving:
14170         DisplayError(_("Warning: You are still observing a game"), 0);
14171         break;
14172       case IcsExamining:
14173         DisplayError(_("Warning: You are still examining a game"), 0);
14174         break;
14175       case IcsIdle:
14176         break;
14177       case EditGame:
14178       default:
14179         return;
14180     }
14181
14182     pausing = FALSE;
14183     StopClocks();
14184     first.offeredDraw = second.offeredDraw = 0;
14185
14186     if (gameMode == PlayFromGameFile) {
14187         whiteTimeRemaining = timeRemaining[0][currentMove];
14188         blackTimeRemaining = timeRemaining[1][currentMove];
14189         DisplayTitle("");
14190     }
14191
14192     if (gameMode == MachinePlaysWhite ||
14193         gameMode == MachinePlaysBlack ||
14194         gameMode == TwoMachinesPlay ||
14195         gameMode == EndOfGame) {
14196         i = forwardMostMove;
14197         while (i > currentMove) {
14198             SendToProgram("undo\n", &first);
14199             i--;
14200         }
14201         if(!adjustedClock) {
14202         whiteTimeRemaining = timeRemaining[0][currentMove];
14203         blackTimeRemaining = timeRemaining[1][currentMove];
14204         DisplayBothClocks();
14205         }
14206         if (whiteFlag || blackFlag) {
14207             whiteFlag = blackFlag = 0;
14208         }
14209         DisplayTitle("");
14210     }
14211
14212     gameMode = EditGame;
14213     ModeHighlight();
14214     SetGameInfo();
14215 }
14216
14217
14218 void
14219 EditPositionEvent ()
14220 {
14221     if (gameMode == EditPosition) {
14222         EditGameEvent();
14223         return;
14224     }
14225
14226     EditGameEvent();
14227     if (gameMode != EditGame) return;
14228
14229     gameMode = EditPosition;
14230     ModeHighlight();
14231     SetGameInfo();
14232     if (currentMove > 0)
14233       CopyBoard(boards[0], boards[currentMove]);
14234
14235     blackPlaysFirst = !WhiteOnMove(currentMove);
14236     ResetClocks();
14237     currentMove = forwardMostMove = backwardMostMove = 0;
14238     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14239     DisplayMove(-1);
14240     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14241 }
14242
14243 void
14244 ExitAnalyzeMode ()
14245 {
14246     /* [DM] icsEngineAnalyze - possible call from other functions */
14247     if (appData.icsEngineAnalyze) {
14248         appData.icsEngineAnalyze = FALSE;
14249
14250         DisplayMessage("",_("Close ICS engine analyze..."));
14251     }
14252     if (first.analysisSupport && first.analyzing) {
14253       SendToBoth("exit\n");
14254       first.analyzing = second.analyzing = FALSE;
14255     }
14256     thinkOutput[0] = NULLCHAR;
14257 }
14258
14259 void
14260 EditPositionDone (Boolean fakeRights)
14261 {
14262     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14263
14264     startedFromSetupPosition = TRUE;
14265     InitChessProgram(&first, FALSE);
14266     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14267       boards[0][EP_STATUS] = EP_NONE;
14268       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14269       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14270         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14271         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14272       } else boards[0][CASTLING][2] = NoRights;
14273       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14274         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14275         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14276       } else boards[0][CASTLING][5] = NoRights;
14277       if(gameInfo.variant == VariantSChess) {
14278         int i;
14279         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14280           boards[0][VIRGIN][i] = 0;
14281           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14282           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14283         }
14284       }
14285     }
14286     SendToProgram("force\n", &first);
14287     if (blackPlaysFirst) {
14288         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14289         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14290         currentMove = forwardMostMove = backwardMostMove = 1;
14291         CopyBoard(boards[1], boards[0]);
14292     } else {
14293         currentMove = forwardMostMove = backwardMostMove = 0;
14294     }
14295     SendBoard(&first, forwardMostMove);
14296     if (appData.debugMode) {
14297         fprintf(debugFP, "EditPosDone\n");
14298     }
14299     DisplayTitle("");
14300     DisplayMessage("", "");
14301     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14302     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14303     gameMode = EditGame;
14304     ModeHighlight();
14305     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14306     ClearHighlights(); /* [AS] */
14307 }
14308
14309 /* Pause for `ms' milliseconds */
14310 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14311 void
14312 TimeDelay (long ms)
14313 {
14314     TimeMark m1, m2;
14315
14316     GetTimeMark(&m1);
14317     do {
14318         GetTimeMark(&m2);
14319     } while (SubtractTimeMarks(&m2, &m1) < ms);
14320 }
14321
14322 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14323 void
14324 SendMultiLineToICS (char *buf)
14325 {
14326     char temp[MSG_SIZ+1], *p;
14327     int len;
14328
14329     len = strlen(buf);
14330     if (len > MSG_SIZ)
14331       len = MSG_SIZ;
14332
14333     strncpy(temp, buf, len);
14334     temp[len] = 0;
14335
14336     p = temp;
14337     while (*p) {
14338         if (*p == '\n' || *p == '\r')
14339           *p = ' ';
14340         ++p;
14341     }
14342
14343     strcat(temp, "\n");
14344     SendToICS(temp);
14345     SendToPlayer(temp, strlen(temp));
14346 }
14347
14348 void
14349 SetWhiteToPlayEvent ()
14350 {
14351     if (gameMode == EditPosition) {
14352         blackPlaysFirst = FALSE;
14353         DisplayBothClocks();    /* works because currentMove is 0 */
14354     } else if (gameMode == IcsExamining) {
14355         SendToICS(ics_prefix);
14356         SendToICS("tomove white\n");
14357     }
14358 }
14359
14360 void
14361 SetBlackToPlayEvent ()
14362 {
14363     if (gameMode == EditPosition) {
14364         blackPlaysFirst = TRUE;
14365         currentMove = 1;        /* kludge */
14366         DisplayBothClocks();
14367         currentMove = 0;
14368     } else if (gameMode == IcsExamining) {
14369         SendToICS(ics_prefix);
14370         SendToICS("tomove black\n");
14371     }
14372 }
14373
14374 void
14375 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14376 {
14377     char buf[MSG_SIZ];
14378     ChessSquare piece = boards[0][y][x];
14379
14380     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14381
14382     switch (selection) {
14383       case ClearBoard:
14384         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14385             SendToICS(ics_prefix);
14386             SendToICS("bsetup clear\n");
14387         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14388             SendToICS(ics_prefix);
14389             SendToICS("clearboard\n");
14390         } else {
14391             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14392                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14393                 for (y = 0; y < BOARD_HEIGHT; y++) {
14394                     if (gameMode == IcsExamining) {
14395                         if (boards[currentMove][y][x] != EmptySquare) {
14396                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14397                                     AAA + x, ONE + y);
14398                             SendToICS(buf);
14399                         }
14400                     } else {
14401                         boards[0][y][x] = p;
14402                     }
14403                 }
14404             }
14405         }
14406         if (gameMode == EditPosition) {
14407             DrawPosition(FALSE, boards[0]);
14408         }
14409         break;
14410
14411       case WhitePlay:
14412         SetWhiteToPlayEvent();
14413         break;
14414
14415       case BlackPlay:
14416         SetBlackToPlayEvent();
14417         break;
14418
14419       case EmptySquare:
14420         if (gameMode == IcsExamining) {
14421             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14422             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14423             SendToICS(buf);
14424         } else {
14425             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14426                 if(x == BOARD_LEFT-2) {
14427                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14428                     boards[0][y][1] = 0;
14429                 } else
14430                 if(x == BOARD_RGHT+1) {
14431                     if(y >= gameInfo.holdingsSize) break;
14432                     boards[0][y][BOARD_WIDTH-2] = 0;
14433                 } else break;
14434             }
14435             boards[0][y][x] = EmptySquare;
14436             DrawPosition(FALSE, boards[0]);
14437         }
14438         break;
14439
14440       case PromotePiece:
14441         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14442            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14443             selection = (ChessSquare) (PROMOTED piece);
14444         } else if(piece == EmptySquare) selection = WhiteSilver;
14445         else selection = (ChessSquare)((int)piece - 1);
14446         goto defaultlabel;
14447
14448       case DemotePiece:
14449         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14450            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14451             selection = (ChessSquare) (DEMOTED piece);
14452         } else if(piece == EmptySquare) selection = BlackSilver;
14453         else selection = (ChessSquare)((int)piece + 1);
14454         goto defaultlabel;
14455
14456       case WhiteQueen:
14457       case BlackQueen:
14458         if(gameInfo.variant == VariantShatranj ||
14459            gameInfo.variant == VariantXiangqi  ||
14460            gameInfo.variant == VariantCourier  ||
14461            gameInfo.variant == VariantMakruk     )
14462             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14463         goto defaultlabel;
14464
14465       case WhiteKing:
14466       case BlackKing:
14467         if(gameInfo.variant == VariantXiangqi)
14468             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14469         if(gameInfo.variant == VariantKnightmate)
14470             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14471       default:
14472         defaultlabel:
14473         if (gameMode == IcsExamining) {
14474             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14475             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14476                      PieceToChar(selection), AAA + x, ONE + y);
14477             SendToICS(buf);
14478         } else {
14479             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14480                 int n;
14481                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14482                     n = PieceToNumber(selection - BlackPawn);
14483                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14484                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14485                     boards[0][BOARD_HEIGHT-1-n][1]++;
14486                 } else
14487                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14488                     n = PieceToNumber(selection);
14489                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14490                     boards[0][n][BOARD_WIDTH-1] = selection;
14491                     boards[0][n][BOARD_WIDTH-2]++;
14492                 }
14493             } else
14494             boards[0][y][x] = selection;
14495             DrawPosition(TRUE, boards[0]);
14496             ClearHighlights();
14497             fromX = fromY = -1;
14498         }
14499         break;
14500     }
14501 }
14502
14503
14504 void
14505 DropMenuEvent (ChessSquare selection, int x, int y)
14506 {
14507     ChessMove moveType;
14508
14509     switch (gameMode) {
14510       case IcsPlayingWhite:
14511       case MachinePlaysBlack:
14512         if (!WhiteOnMove(currentMove)) {
14513             DisplayMoveError(_("It is Black's turn"));
14514             return;
14515         }
14516         moveType = WhiteDrop;
14517         break;
14518       case IcsPlayingBlack:
14519       case MachinePlaysWhite:
14520         if (WhiteOnMove(currentMove)) {
14521             DisplayMoveError(_("It is White's turn"));
14522             return;
14523         }
14524         moveType = BlackDrop;
14525         break;
14526       case EditGame:
14527         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14528         break;
14529       default:
14530         return;
14531     }
14532
14533     if (moveType == BlackDrop && selection < BlackPawn) {
14534       selection = (ChessSquare) ((int) selection
14535                                  + (int) BlackPawn - (int) WhitePawn);
14536     }
14537     if (boards[currentMove][y][x] != EmptySquare) {
14538         DisplayMoveError(_("That square is occupied"));
14539         return;
14540     }
14541
14542     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14543 }
14544
14545 void
14546 AcceptEvent ()
14547 {
14548     /* Accept a pending offer of any kind from opponent */
14549
14550     if (appData.icsActive) {
14551         SendToICS(ics_prefix);
14552         SendToICS("accept\n");
14553     } else if (cmailMsgLoaded) {
14554         if (currentMove == cmailOldMove &&
14555             commentList[cmailOldMove] != NULL &&
14556             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14557                    "Black offers a draw" : "White offers a draw")) {
14558             TruncateGame();
14559             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14560             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14561         } else {
14562             DisplayError(_("There is no pending offer on this move"), 0);
14563             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14564         }
14565     } else {
14566         /* Not used for offers from chess program */
14567     }
14568 }
14569
14570 void
14571 DeclineEvent ()
14572 {
14573     /* Decline a pending offer of any kind from opponent */
14574
14575     if (appData.icsActive) {
14576         SendToICS(ics_prefix);
14577         SendToICS("decline\n");
14578     } else if (cmailMsgLoaded) {
14579         if (currentMove == cmailOldMove &&
14580             commentList[cmailOldMove] != NULL &&
14581             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14582                    "Black offers a draw" : "White offers a draw")) {
14583 #ifdef NOTDEF
14584             AppendComment(cmailOldMove, "Draw declined", TRUE);
14585             DisplayComment(cmailOldMove - 1, "Draw declined");
14586 #endif /*NOTDEF*/
14587         } else {
14588             DisplayError(_("There is no pending offer on this move"), 0);
14589         }
14590     } else {
14591         /* Not used for offers from chess program */
14592     }
14593 }
14594
14595 void
14596 RematchEvent ()
14597 {
14598     /* Issue ICS rematch command */
14599     if (appData.icsActive) {
14600         SendToICS(ics_prefix);
14601         SendToICS("rematch\n");
14602     }
14603 }
14604
14605 void
14606 CallFlagEvent ()
14607 {
14608     /* Call your opponent's flag (claim a win on time) */
14609     if (appData.icsActive) {
14610         SendToICS(ics_prefix);
14611         SendToICS("flag\n");
14612     } else {
14613         switch (gameMode) {
14614           default:
14615             return;
14616           case MachinePlaysWhite:
14617             if (whiteFlag) {
14618                 if (blackFlag)
14619                   GameEnds(GameIsDrawn, "Both players ran out of time",
14620                            GE_PLAYER);
14621                 else
14622                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14623             } else {
14624                 DisplayError(_("Your opponent is not out of time"), 0);
14625             }
14626             break;
14627           case MachinePlaysBlack:
14628             if (blackFlag) {
14629                 if (whiteFlag)
14630                   GameEnds(GameIsDrawn, "Both players ran out of time",
14631                            GE_PLAYER);
14632                 else
14633                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14634             } else {
14635                 DisplayError(_("Your opponent is not out of time"), 0);
14636             }
14637             break;
14638         }
14639     }
14640 }
14641
14642 void
14643 ClockClick (int which)
14644 {       // [HGM] code moved to back-end from winboard.c
14645         if(which) { // black clock
14646           if (gameMode == EditPosition || gameMode == IcsExamining) {
14647             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14648             SetBlackToPlayEvent();
14649           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14650           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14651           } else if (shiftKey) {
14652             AdjustClock(which, -1);
14653           } else if (gameMode == IcsPlayingWhite ||
14654                      gameMode == MachinePlaysBlack) {
14655             CallFlagEvent();
14656           }
14657         } else { // white clock
14658           if (gameMode == EditPosition || gameMode == IcsExamining) {
14659             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14660             SetWhiteToPlayEvent();
14661           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14662           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14663           } else if (shiftKey) {
14664             AdjustClock(which, -1);
14665           } else if (gameMode == IcsPlayingBlack ||
14666                    gameMode == MachinePlaysWhite) {
14667             CallFlagEvent();
14668           }
14669         }
14670 }
14671
14672 void
14673 DrawEvent ()
14674 {
14675     /* Offer draw or accept pending draw offer from opponent */
14676
14677     if (appData.icsActive) {
14678         /* Note: tournament rules require draw offers to be
14679            made after you make your move but before you punch
14680            your clock.  Currently ICS doesn't let you do that;
14681            instead, you immediately punch your clock after making
14682            a move, but you can offer a draw at any time. */
14683
14684         SendToICS(ics_prefix);
14685         SendToICS("draw\n");
14686         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14687     } else if (cmailMsgLoaded) {
14688         if (currentMove == cmailOldMove &&
14689             commentList[cmailOldMove] != NULL &&
14690             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14691                    "Black offers a draw" : "White offers a draw")) {
14692             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14693             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14694         } else if (currentMove == cmailOldMove + 1) {
14695             char *offer = WhiteOnMove(cmailOldMove) ?
14696               "White offers a draw" : "Black offers a draw";
14697             AppendComment(currentMove, offer, TRUE);
14698             DisplayComment(currentMove - 1, offer);
14699             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14700         } else {
14701             DisplayError(_("You must make your move before offering a draw"), 0);
14702             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14703         }
14704     } else if (first.offeredDraw) {
14705         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14706     } else {
14707         if (first.sendDrawOffers) {
14708             SendToProgram("draw\n", &first);
14709             userOfferedDraw = TRUE;
14710         }
14711     }
14712 }
14713
14714 void
14715 AdjournEvent ()
14716 {
14717     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14718
14719     if (appData.icsActive) {
14720         SendToICS(ics_prefix);
14721         SendToICS("adjourn\n");
14722     } else {
14723         /* Currently GNU Chess doesn't offer or accept Adjourns */
14724     }
14725 }
14726
14727
14728 void
14729 AbortEvent ()
14730 {
14731     /* Offer Abort or accept pending Abort offer from opponent */
14732
14733     if (appData.icsActive) {
14734         SendToICS(ics_prefix);
14735         SendToICS("abort\n");
14736     } else {
14737         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14738     }
14739 }
14740
14741 void
14742 ResignEvent ()
14743 {
14744     /* Resign.  You can do this even if it's not your turn. */
14745
14746     if (appData.icsActive) {
14747         SendToICS(ics_prefix);
14748         SendToICS("resign\n");
14749     } else {
14750         switch (gameMode) {
14751           case MachinePlaysWhite:
14752             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14753             break;
14754           case MachinePlaysBlack:
14755             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14756             break;
14757           case EditGame:
14758             if (cmailMsgLoaded) {
14759                 TruncateGame();
14760                 if (WhiteOnMove(cmailOldMove)) {
14761                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14762                 } else {
14763                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14764                 }
14765                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14766             }
14767             break;
14768           default:
14769             break;
14770         }
14771     }
14772 }
14773
14774
14775 void
14776 StopObservingEvent ()
14777 {
14778     /* Stop observing current games */
14779     SendToICS(ics_prefix);
14780     SendToICS("unobserve\n");
14781 }
14782
14783 void
14784 StopExaminingEvent ()
14785 {
14786     /* Stop observing current game */
14787     SendToICS(ics_prefix);
14788     SendToICS("unexamine\n");
14789 }
14790
14791 void
14792 ForwardInner (int target)
14793 {
14794     int limit; int oldSeekGraphUp = seekGraphUp;
14795
14796     if (appData.debugMode)
14797         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14798                 target, currentMove, forwardMostMove);
14799
14800     if (gameMode == EditPosition)
14801       return;
14802
14803     seekGraphUp = FALSE;
14804     MarkTargetSquares(1);
14805
14806     if (gameMode == PlayFromGameFile && !pausing)
14807       PauseEvent();
14808
14809     if (gameMode == IcsExamining && pausing)
14810       limit = pauseExamForwardMostMove;
14811     else
14812       limit = forwardMostMove;
14813
14814     if (target > limit) target = limit;
14815
14816     if (target > 0 && moveList[target - 1][0]) {
14817         int fromX, fromY, toX, toY;
14818         toX = moveList[target - 1][2] - AAA;
14819         toY = moveList[target - 1][3] - ONE;
14820         if (moveList[target - 1][1] == '@') {
14821             if (appData.highlightLastMove) {
14822                 SetHighlights(-1, -1, toX, toY);
14823             }
14824         } else {
14825             fromX = moveList[target - 1][0] - AAA;
14826             fromY = moveList[target - 1][1] - ONE;
14827             if (target == currentMove + 1) {
14828                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14829             }
14830             if (appData.highlightLastMove) {
14831                 SetHighlights(fromX, fromY, toX, toY);
14832             }
14833         }
14834     }
14835     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14836         gameMode == Training || gameMode == PlayFromGameFile ||
14837         gameMode == AnalyzeFile) {
14838         while (currentMove < target) {
14839             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14840             SendMoveToProgram(currentMove++, &first);
14841         }
14842     } else {
14843         currentMove = target;
14844     }
14845
14846     if (gameMode == EditGame || gameMode == EndOfGame) {
14847         whiteTimeRemaining = timeRemaining[0][currentMove];
14848         blackTimeRemaining = timeRemaining[1][currentMove];
14849     }
14850     DisplayBothClocks();
14851     DisplayMove(currentMove - 1);
14852     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14853     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14854     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14855         DisplayComment(currentMove - 1, commentList[currentMove]);
14856     }
14857     ClearMap(); // [HGM] exclude: invalidate map
14858 }
14859
14860
14861 void
14862 ForwardEvent ()
14863 {
14864     if (gameMode == IcsExamining && !pausing) {
14865         SendToICS(ics_prefix);
14866         SendToICS("forward\n");
14867     } else {
14868         ForwardInner(currentMove + 1);
14869     }
14870 }
14871
14872 void
14873 ToEndEvent ()
14874 {
14875     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14876         /* to optimze, we temporarily turn off analysis mode while we feed
14877          * the remaining moves to the engine. Otherwise we get analysis output
14878          * after each move.
14879          */
14880         if (first.analysisSupport) {
14881           SendToProgram("exit\nforce\n", &first);
14882           first.analyzing = FALSE;
14883         }
14884     }
14885
14886     if (gameMode == IcsExamining && !pausing) {
14887         SendToICS(ics_prefix);
14888         SendToICS("forward 999999\n");
14889     } else {
14890         ForwardInner(forwardMostMove);
14891     }
14892
14893     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14894         /* we have fed all the moves, so reactivate analysis mode */
14895         SendToProgram("analyze\n", &first);
14896         first.analyzing = TRUE;
14897         /*first.maybeThinking = TRUE;*/
14898         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14899     }
14900 }
14901
14902 void
14903 BackwardInner (int target)
14904 {
14905     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14906
14907     if (appData.debugMode)
14908         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14909                 target, currentMove, forwardMostMove);
14910
14911     if (gameMode == EditPosition) return;
14912     seekGraphUp = FALSE;
14913     MarkTargetSquares(1);
14914     if (currentMove <= backwardMostMove) {
14915         ClearHighlights();
14916         DrawPosition(full_redraw, boards[currentMove]);
14917         return;
14918     }
14919     if (gameMode == PlayFromGameFile && !pausing)
14920       PauseEvent();
14921
14922     if (moveList[target][0]) {
14923         int fromX, fromY, toX, toY;
14924         toX = moveList[target][2] - AAA;
14925         toY = moveList[target][3] - ONE;
14926         if (moveList[target][1] == '@') {
14927             if (appData.highlightLastMove) {
14928                 SetHighlights(-1, -1, toX, toY);
14929             }
14930         } else {
14931             fromX = moveList[target][0] - AAA;
14932             fromY = moveList[target][1] - ONE;
14933             if (target == currentMove - 1) {
14934                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14935             }
14936             if (appData.highlightLastMove) {
14937                 SetHighlights(fromX, fromY, toX, toY);
14938             }
14939         }
14940     }
14941     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14942         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14943         while (currentMove > target) {
14944             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14945                 // null move cannot be undone. Reload program with move history before it.
14946                 int i;
14947                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14948                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14949                 }
14950                 SendBoard(&first, i);
14951               if(second.analyzing) SendBoard(&second, i);
14952                 for(currentMove=i; currentMove<target; currentMove++) {
14953                     SendMoveToProgram(currentMove, &first);
14954                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14955                 }
14956                 break;
14957             }
14958             SendToBoth("undo\n");
14959             currentMove--;
14960         }
14961     } else {
14962         currentMove = target;
14963     }
14964
14965     if (gameMode == EditGame || gameMode == EndOfGame) {
14966         whiteTimeRemaining = timeRemaining[0][currentMove];
14967         blackTimeRemaining = timeRemaining[1][currentMove];
14968     }
14969     DisplayBothClocks();
14970     DisplayMove(currentMove - 1);
14971     DrawPosition(full_redraw, boards[currentMove]);
14972     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14973     // [HGM] PV info: routine tests if comment empty
14974     DisplayComment(currentMove - 1, commentList[currentMove]);
14975     ClearMap(); // [HGM] exclude: invalidate map
14976 }
14977
14978 void
14979 BackwardEvent ()
14980 {
14981     if (gameMode == IcsExamining && !pausing) {
14982         SendToICS(ics_prefix);
14983         SendToICS("backward\n");
14984     } else {
14985         BackwardInner(currentMove - 1);
14986     }
14987 }
14988
14989 void
14990 ToStartEvent ()
14991 {
14992     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14993         /* to optimize, we temporarily turn off analysis mode while we undo
14994          * all the moves. Otherwise we get analysis output after each undo.
14995          */
14996         if (first.analysisSupport) {
14997           SendToProgram("exit\nforce\n", &first);
14998           first.analyzing = FALSE;
14999         }
15000     }
15001
15002     if (gameMode == IcsExamining && !pausing) {
15003         SendToICS(ics_prefix);
15004         SendToICS("backward 999999\n");
15005     } else {
15006         BackwardInner(backwardMostMove);
15007     }
15008
15009     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15010         /* we have fed all the moves, so reactivate analysis mode */
15011         SendToProgram("analyze\n", &first);
15012         first.analyzing = TRUE;
15013         /*first.maybeThinking = TRUE;*/
15014         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15015     }
15016 }
15017
15018 void
15019 ToNrEvent (int to)
15020 {
15021   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15022   if (to >= forwardMostMove) to = forwardMostMove;
15023   if (to <= backwardMostMove) to = backwardMostMove;
15024   if (to < currentMove) {
15025     BackwardInner(to);
15026   } else {
15027     ForwardInner(to);
15028   }
15029 }
15030
15031 void
15032 RevertEvent (Boolean annotate)
15033 {
15034     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15035         return;
15036     }
15037     if (gameMode != IcsExamining) {
15038         DisplayError(_("You are not examining a game"), 0);
15039         return;
15040     }
15041     if (pausing) {
15042         DisplayError(_("You can't revert while pausing"), 0);
15043         return;
15044     }
15045     SendToICS(ics_prefix);
15046     SendToICS("revert\n");
15047 }
15048
15049 void
15050 RetractMoveEvent ()
15051 {
15052     switch (gameMode) {
15053       case MachinePlaysWhite:
15054       case MachinePlaysBlack:
15055         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15056             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15057             return;
15058         }
15059         if (forwardMostMove < 2) return;
15060         currentMove = forwardMostMove = forwardMostMove - 2;
15061         whiteTimeRemaining = timeRemaining[0][currentMove];
15062         blackTimeRemaining = timeRemaining[1][currentMove];
15063         DisplayBothClocks();
15064         DisplayMove(currentMove - 1);
15065         ClearHighlights();/*!! could figure this out*/
15066         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15067         SendToProgram("remove\n", &first);
15068         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15069         break;
15070
15071       case BeginningOfGame:
15072       default:
15073         break;
15074
15075       case IcsPlayingWhite:
15076       case IcsPlayingBlack:
15077         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15078             SendToICS(ics_prefix);
15079             SendToICS("takeback 2\n");
15080         } else {
15081             SendToICS(ics_prefix);
15082             SendToICS("takeback 1\n");
15083         }
15084         break;
15085     }
15086 }
15087
15088 void
15089 MoveNowEvent ()
15090 {
15091     ChessProgramState *cps;
15092
15093     switch (gameMode) {
15094       case MachinePlaysWhite:
15095         if (!WhiteOnMove(forwardMostMove)) {
15096             DisplayError(_("It is your turn"), 0);
15097             return;
15098         }
15099         cps = &first;
15100         break;
15101       case MachinePlaysBlack:
15102         if (WhiteOnMove(forwardMostMove)) {
15103             DisplayError(_("It is your turn"), 0);
15104             return;
15105         }
15106         cps = &first;
15107         break;
15108       case TwoMachinesPlay:
15109         if (WhiteOnMove(forwardMostMove) ==
15110             (first.twoMachinesColor[0] == 'w')) {
15111             cps = &first;
15112         } else {
15113             cps = &second;
15114         }
15115         break;
15116       case BeginningOfGame:
15117       default:
15118         return;
15119     }
15120     SendToProgram("?\n", cps);
15121 }
15122
15123 void
15124 TruncateGameEvent ()
15125 {
15126     EditGameEvent();
15127     if (gameMode != EditGame) return;
15128     TruncateGame();
15129 }
15130
15131 void
15132 TruncateGame ()
15133 {
15134     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15135     if (forwardMostMove > currentMove) {
15136         if (gameInfo.resultDetails != NULL) {
15137             free(gameInfo.resultDetails);
15138             gameInfo.resultDetails = NULL;
15139             gameInfo.result = GameUnfinished;
15140         }
15141         forwardMostMove = currentMove;
15142         HistorySet(parseList, backwardMostMove, forwardMostMove,
15143                    currentMove-1);
15144     }
15145 }
15146
15147 void
15148 HintEvent ()
15149 {
15150     if (appData.noChessProgram) return;
15151     switch (gameMode) {
15152       case MachinePlaysWhite:
15153         if (WhiteOnMove(forwardMostMove)) {
15154             DisplayError(_("Wait until your turn"), 0);
15155             return;
15156         }
15157         break;
15158       case BeginningOfGame:
15159       case MachinePlaysBlack:
15160         if (!WhiteOnMove(forwardMostMove)) {
15161             DisplayError(_("Wait until your turn"), 0);
15162             return;
15163         }
15164         break;
15165       default:
15166         DisplayError(_("No hint available"), 0);
15167         return;
15168     }
15169     SendToProgram("hint\n", &first);
15170     hintRequested = TRUE;
15171 }
15172
15173 void
15174 CreateBookEvent ()
15175 {
15176     ListGame * lg = (ListGame *) gameList.head;
15177     FILE *f;
15178     int nItem;
15179     static int secondTime = FALSE;
15180
15181     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15182         DisplayError(_("Game list not loaded or empty"), 0);
15183         return;
15184     }
15185
15186     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15187         fclose(f);
15188         secondTime++;
15189         DisplayNote(_("Book file exists! Try again for overwrite."));
15190         return;
15191     }
15192
15193     creatingBook = TRUE;
15194     secondTime = FALSE;
15195
15196     /* Get list size */
15197     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15198         LoadGame(f, nItem, "", TRUE);
15199         AddGameToBook(TRUE);
15200         lg = (ListGame *) lg->node.succ;
15201     }
15202
15203     creatingBook = FALSE;
15204     FlushBook();
15205 }
15206
15207 void
15208 BookEvent ()
15209 {
15210     if (appData.noChessProgram) return;
15211     switch (gameMode) {
15212       case MachinePlaysWhite:
15213         if (WhiteOnMove(forwardMostMove)) {
15214             DisplayError(_("Wait until your turn"), 0);
15215             return;
15216         }
15217         break;
15218       case BeginningOfGame:
15219       case MachinePlaysBlack:
15220         if (!WhiteOnMove(forwardMostMove)) {
15221             DisplayError(_("Wait until your turn"), 0);
15222             return;
15223         }
15224         break;
15225       case EditPosition:
15226         EditPositionDone(TRUE);
15227         break;
15228       case TwoMachinesPlay:
15229         return;
15230       default:
15231         break;
15232     }
15233     SendToProgram("bk\n", &first);
15234     bookOutput[0] = NULLCHAR;
15235     bookRequested = TRUE;
15236 }
15237
15238 void
15239 AboutGameEvent ()
15240 {
15241     char *tags = PGNTags(&gameInfo);
15242     TagsPopUp(tags, CmailMsg());
15243     free(tags);
15244 }
15245
15246 /* end button procedures */
15247
15248 void
15249 PrintPosition (FILE *fp, int move)
15250 {
15251     int i, j;
15252
15253     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15254         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15255             char c = PieceToChar(boards[move][i][j]);
15256             fputc(c == 'x' ? '.' : c, fp);
15257             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15258         }
15259     }
15260     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15261       fprintf(fp, "white to play\n");
15262     else
15263       fprintf(fp, "black to play\n");
15264 }
15265
15266 void
15267 PrintOpponents (FILE *fp)
15268 {
15269     if (gameInfo.white != NULL) {
15270         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15271     } else {
15272         fprintf(fp, "\n");
15273     }
15274 }
15275
15276 /* Find last component of program's own name, using some heuristics */
15277 void
15278 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15279 {
15280     char *p, *q, c;
15281     int local = (strcmp(host, "localhost") == 0);
15282     while (!local && (p = strchr(prog, ';')) != NULL) {
15283         p++;
15284         while (*p == ' ') p++;
15285         prog = p;
15286     }
15287     if (*prog == '"' || *prog == '\'') {
15288         q = strchr(prog + 1, *prog);
15289     } else {
15290         q = strchr(prog, ' ');
15291     }
15292     if (q == NULL) q = prog + strlen(prog);
15293     p = q;
15294     while (p >= prog && *p != '/' && *p != '\\') p--;
15295     p++;
15296     if(p == prog && *p == '"') p++;
15297     c = *q; *q = 0;
15298     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15299     memcpy(buf, p, q - p);
15300     buf[q - p] = NULLCHAR;
15301     if (!local) {
15302         strcat(buf, "@");
15303         strcat(buf, host);
15304     }
15305 }
15306
15307 char *
15308 TimeControlTagValue ()
15309 {
15310     char buf[MSG_SIZ];
15311     if (!appData.clockMode) {
15312       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15313     } else if (movesPerSession > 0) {
15314       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15315     } else if (timeIncrement == 0) {
15316       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15317     } else {
15318       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15319     }
15320     return StrSave(buf);
15321 }
15322
15323 void
15324 SetGameInfo ()
15325 {
15326     /* This routine is used only for certain modes */
15327     VariantClass v = gameInfo.variant;
15328     ChessMove r = GameUnfinished;
15329     char *p = NULL;
15330
15331     if(keepInfo) return;
15332
15333     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15334         r = gameInfo.result;
15335         p = gameInfo.resultDetails;
15336         gameInfo.resultDetails = NULL;
15337     }
15338     ClearGameInfo(&gameInfo);
15339     gameInfo.variant = v;
15340
15341     switch (gameMode) {
15342       case MachinePlaysWhite:
15343         gameInfo.event = StrSave( appData.pgnEventHeader );
15344         gameInfo.site = StrSave(HostName());
15345         gameInfo.date = PGNDate();
15346         gameInfo.round = StrSave("-");
15347         gameInfo.white = StrSave(first.tidy);
15348         gameInfo.black = StrSave(UserName());
15349         gameInfo.timeControl = TimeControlTagValue();
15350         break;
15351
15352       case MachinePlaysBlack:
15353         gameInfo.event = StrSave( appData.pgnEventHeader );
15354         gameInfo.site = StrSave(HostName());
15355         gameInfo.date = PGNDate();
15356         gameInfo.round = StrSave("-");
15357         gameInfo.white = StrSave(UserName());
15358         gameInfo.black = StrSave(first.tidy);
15359         gameInfo.timeControl = TimeControlTagValue();
15360         break;
15361
15362       case TwoMachinesPlay:
15363         gameInfo.event = StrSave( appData.pgnEventHeader );
15364         gameInfo.site = StrSave(HostName());
15365         gameInfo.date = PGNDate();
15366         if (roundNr > 0) {
15367             char buf[MSG_SIZ];
15368             snprintf(buf, MSG_SIZ, "%d", roundNr);
15369             gameInfo.round = StrSave(buf);
15370         } else {
15371             gameInfo.round = StrSave("-");
15372         }
15373         if (first.twoMachinesColor[0] == 'w') {
15374             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15375             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15376         } else {
15377             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15378             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15379         }
15380         gameInfo.timeControl = TimeControlTagValue();
15381         break;
15382
15383       case EditGame:
15384         gameInfo.event = StrSave("Edited game");
15385         gameInfo.site = StrSave(HostName());
15386         gameInfo.date = PGNDate();
15387         gameInfo.round = StrSave("-");
15388         gameInfo.white = StrSave("-");
15389         gameInfo.black = StrSave("-");
15390         gameInfo.result = r;
15391         gameInfo.resultDetails = p;
15392         break;
15393
15394       case EditPosition:
15395         gameInfo.event = StrSave("Edited position");
15396         gameInfo.site = StrSave(HostName());
15397         gameInfo.date = PGNDate();
15398         gameInfo.round = StrSave("-");
15399         gameInfo.white = StrSave("-");
15400         gameInfo.black = StrSave("-");
15401         break;
15402
15403       case IcsPlayingWhite:
15404       case IcsPlayingBlack:
15405       case IcsObserving:
15406       case IcsExamining:
15407         break;
15408
15409       case PlayFromGameFile:
15410         gameInfo.event = StrSave("Game from non-PGN file");
15411         gameInfo.site = StrSave(HostName());
15412         gameInfo.date = PGNDate();
15413         gameInfo.round = StrSave("-");
15414         gameInfo.white = StrSave("?");
15415         gameInfo.black = StrSave("?");
15416         break;
15417
15418       default:
15419         break;
15420     }
15421 }
15422
15423 void
15424 ReplaceComment (int index, char *text)
15425 {
15426     int len;
15427     char *p;
15428     float score;
15429
15430     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15431        pvInfoList[index-1].depth == len &&
15432        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15433        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15434     while (*text == '\n') text++;
15435     len = strlen(text);
15436     while (len > 0 && text[len - 1] == '\n') len--;
15437
15438     if (commentList[index] != NULL)
15439       free(commentList[index]);
15440
15441     if (len == 0) {
15442         commentList[index] = NULL;
15443         return;
15444     }
15445   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15446       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15447       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15448     commentList[index] = (char *) malloc(len + 2);
15449     strncpy(commentList[index], text, len);
15450     commentList[index][len] = '\n';
15451     commentList[index][len + 1] = NULLCHAR;
15452   } else {
15453     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15454     char *p;
15455     commentList[index] = (char *) malloc(len + 7);
15456     safeStrCpy(commentList[index], "{\n", 3);
15457     safeStrCpy(commentList[index]+2, text, len+1);
15458     commentList[index][len+2] = NULLCHAR;
15459     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15460     strcat(commentList[index], "\n}\n");
15461   }
15462 }
15463
15464 void
15465 CrushCRs (char *text)
15466 {
15467   char *p = text;
15468   char *q = text;
15469   char ch;
15470
15471   do {
15472     ch = *p++;
15473     if (ch == '\r') continue;
15474     *q++ = ch;
15475   } while (ch != '\0');
15476 }
15477
15478 void
15479 AppendComment (int index, char *text, Boolean addBraces)
15480 /* addBraces  tells if we should add {} */
15481 {
15482     int oldlen, len;
15483     char *old;
15484
15485 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15486     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15487
15488     CrushCRs(text);
15489     while (*text == '\n') text++;
15490     len = strlen(text);
15491     while (len > 0 && text[len - 1] == '\n') len--;
15492     text[len] = NULLCHAR;
15493
15494     if (len == 0) return;
15495
15496     if (commentList[index] != NULL) {
15497       Boolean addClosingBrace = addBraces;
15498         old = commentList[index];
15499         oldlen = strlen(old);
15500         while(commentList[index][oldlen-1] ==  '\n')
15501           commentList[index][--oldlen] = NULLCHAR;
15502         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15503         safeStrCpy(commentList[index], old, oldlen + len + 6);
15504         free(old);
15505         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15506         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15507           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15508           while (*text == '\n') { text++; len--; }
15509           commentList[index][--oldlen] = NULLCHAR;
15510       }
15511         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15512         else          strcat(commentList[index], "\n");
15513         strcat(commentList[index], text);
15514         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15515         else          strcat(commentList[index], "\n");
15516     } else {
15517         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15518         if(addBraces)
15519           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15520         else commentList[index][0] = NULLCHAR;
15521         strcat(commentList[index], text);
15522         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15523         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15524     }
15525 }
15526
15527 static char *
15528 FindStr (char * text, char * sub_text)
15529 {
15530     char * result = strstr( text, sub_text );
15531
15532     if( result != NULL ) {
15533         result += strlen( sub_text );
15534     }
15535
15536     return result;
15537 }
15538
15539 /* [AS] Try to extract PV info from PGN comment */
15540 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15541 char *
15542 GetInfoFromComment (int index, char * text)
15543 {
15544     char * sep = text, *p;
15545
15546     if( text != NULL && index > 0 ) {
15547         int score = 0;
15548         int depth = 0;
15549         int time = -1, sec = 0, deci;
15550         char * s_eval = FindStr( text, "[%eval " );
15551         char * s_emt = FindStr( text, "[%emt " );
15552
15553         if( s_eval != NULL || s_emt != NULL ) {
15554             /* New style */
15555             char delim;
15556
15557             if( s_eval != NULL ) {
15558                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15559                     return text;
15560                 }
15561
15562                 if( delim != ']' ) {
15563                     return text;
15564                 }
15565             }
15566
15567             if( s_emt != NULL ) {
15568             }
15569                 return text;
15570         }
15571         else {
15572             /* We expect something like: [+|-]nnn.nn/dd */
15573             int score_lo = 0;
15574
15575             if(*text != '{') return text; // [HGM] braces: must be normal comment
15576
15577             sep = strchr( text, '/' );
15578             if( sep == NULL || sep < (text+4) ) {
15579                 return text;
15580             }
15581
15582             p = text;
15583             if(p[1] == '(') { // comment starts with PV
15584                p = strchr(p, ')'); // locate end of PV
15585                if(p == NULL || sep < p+5) return text;
15586                // at this point we have something like "{(.*) +0.23/6 ..."
15587                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15588                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15589                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15590             }
15591             time = -1; sec = -1; deci = -1;
15592             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15593                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15594                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15595                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15596                 return text;
15597             }
15598
15599             if( score_lo < 0 || score_lo >= 100 ) {
15600                 return text;
15601             }
15602
15603             if(sec >= 0) time = 600*time + 10*sec; else
15604             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15605
15606             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15607
15608             /* [HGM] PV time: now locate end of PV info */
15609             while( *++sep >= '0' && *sep <= '9'); // strip depth
15610             if(time >= 0)
15611             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15612             if(sec >= 0)
15613             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15614             if(deci >= 0)
15615             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15616             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15617         }
15618
15619         if( depth <= 0 ) {
15620             return text;
15621         }
15622
15623         if( time < 0 ) {
15624             time = -1;
15625         }
15626
15627         pvInfoList[index-1].depth = depth;
15628         pvInfoList[index-1].score = score;
15629         pvInfoList[index-1].time  = 10*time; // centi-sec
15630         if(*sep == '}') *sep = 0; else *--sep = '{';
15631         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15632     }
15633     return sep;
15634 }
15635
15636 void
15637 SendToProgram (char *message, ChessProgramState *cps)
15638 {
15639     int count, outCount, error;
15640     char buf[MSG_SIZ];
15641
15642     if (cps->pr == NoProc) return;
15643     Attention(cps);
15644
15645     if (appData.debugMode) {
15646         TimeMark now;
15647         GetTimeMark(&now);
15648         fprintf(debugFP, "%ld >%-6s: %s",
15649                 SubtractTimeMarks(&now, &programStartTime),
15650                 cps->which, message);
15651         if(serverFP)
15652             fprintf(serverFP, "%ld >%-6s: %s",
15653                 SubtractTimeMarks(&now, &programStartTime),
15654                 cps->which, message), fflush(serverFP);
15655     }
15656
15657     count = strlen(message);
15658     outCount = OutputToProcess(cps->pr, message, count, &error);
15659     if (outCount < count && !exiting
15660                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15661       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15662       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15663         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15664             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15665                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15666                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15667                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15668             } else {
15669                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15670                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15671                 gameInfo.result = res;
15672             }
15673             gameInfo.resultDetails = StrSave(buf);
15674         }
15675         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15676         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15677     }
15678 }
15679
15680 void
15681 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15682 {
15683     char *end_str;
15684     char buf[MSG_SIZ];
15685     ChessProgramState *cps = (ChessProgramState *)closure;
15686
15687     if (isr != cps->isr) return; /* Killed intentionally */
15688     if (count <= 0) {
15689         if (count == 0) {
15690             RemoveInputSource(cps->isr);
15691             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15692                     _(cps->which), cps->program);
15693             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15694             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15695                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15696                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15697                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15698                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15699                 } else {
15700                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15701                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15702                     gameInfo.result = res;
15703                 }
15704                 gameInfo.resultDetails = StrSave(buf);
15705             }
15706             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15707             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15708         } else {
15709             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15710                     _(cps->which), cps->program);
15711             RemoveInputSource(cps->isr);
15712
15713             /* [AS] Program is misbehaving badly... kill it */
15714             if( count == -2 ) {
15715                 DestroyChildProcess( cps->pr, 9 );
15716                 cps->pr = NoProc;
15717             }
15718
15719             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15720         }
15721         return;
15722     }
15723
15724     if ((end_str = strchr(message, '\r')) != NULL)
15725       *end_str = NULLCHAR;
15726     if ((end_str = strchr(message, '\n')) != NULL)
15727       *end_str = NULLCHAR;
15728
15729     if (appData.debugMode) {
15730         TimeMark now; int print = 1;
15731         char *quote = ""; char c; int i;
15732
15733         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15734                 char start = message[0];
15735                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15736                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15737                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15738                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15739                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15740                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15741                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15742                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15743                    sscanf(message, "hint: %c", &c)!=1 &&
15744                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15745                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15746                     print = (appData.engineComments >= 2);
15747                 }
15748                 message[0] = start; // restore original message
15749         }
15750         if(print) {
15751                 GetTimeMark(&now);
15752                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15753                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15754                         quote,
15755                         message);
15756                 if(serverFP)
15757                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15758                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15759                         quote,
15760                         message), fflush(serverFP);
15761         }
15762     }
15763
15764     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15765     if (appData.icsEngineAnalyze) {
15766         if (strstr(message, "whisper") != NULL ||
15767              strstr(message, "kibitz") != NULL ||
15768             strstr(message, "tellics") != NULL) return;
15769     }
15770
15771     HandleMachineMove(message, cps);
15772 }
15773
15774
15775 void
15776 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15777 {
15778     char buf[MSG_SIZ];
15779     int seconds;
15780
15781     if( timeControl_2 > 0 ) {
15782         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15783             tc = timeControl_2;
15784         }
15785     }
15786     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15787     inc /= cps->timeOdds;
15788     st  /= cps->timeOdds;
15789
15790     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15791
15792     if (st > 0) {
15793       /* Set exact time per move, normally using st command */
15794       if (cps->stKludge) {
15795         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15796         seconds = st % 60;
15797         if (seconds == 0) {
15798           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15799         } else {
15800           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15801         }
15802       } else {
15803         snprintf(buf, MSG_SIZ, "st %d\n", st);
15804       }
15805     } else {
15806       /* Set conventional or incremental time control, using level command */
15807       if (seconds == 0) {
15808         /* Note old gnuchess bug -- minutes:seconds used to not work.
15809            Fixed in later versions, but still avoid :seconds
15810            when seconds is 0. */
15811         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15812       } else {
15813         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15814                  seconds, inc/1000.);
15815       }
15816     }
15817     SendToProgram(buf, cps);
15818
15819     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15820     /* Orthogonally, limit search to given depth */
15821     if (sd > 0) {
15822       if (cps->sdKludge) {
15823         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15824       } else {
15825         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15826       }
15827       SendToProgram(buf, cps);
15828     }
15829
15830     if(cps->nps >= 0) { /* [HGM] nps */
15831         if(cps->supportsNPS == FALSE)
15832           cps->nps = -1; // don't use if engine explicitly says not supported!
15833         else {
15834           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15835           SendToProgram(buf, cps);
15836         }
15837     }
15838 }
15839
15840 ChessProgramState *
15841 WhitePlayer ()
15842 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15843 {
15844     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15845        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15846         return &second;
15847     return &first;
15848 }
15849
15850 void
15851 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15852 {
15853     char message[MSG_SIZ];
15854     long time, otime;
15855
15856     /* Note: this routine must be called when the clocks are stopped
15857        or when they have *just* been set or switched; otherwise
15858        it will be off by the time since the current tick started.
15859     */
15860     if (machineWhite) {
15861         time = whiteTimeRemaining / 10;
15862         otime = blackTimeRemaining / 10;
15863     } else {
15864         time = blackTimeRemaining / 10;
15865         otime = whiteTimeRemaining / 10;
15866     }
15867     /* [HGM] translate opponent's time by time-odds factor */
15868     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15869
15870     if (time <= 0) time = 1;
15871     if (otime <= 0) otime = 1;
15872
15873     snprintf(message, MSG_SIZ, "time %ld\n", time);
15874     SendToProgram(message, cps);
15875
15876     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15877     SendToProgram(message, cps);
15878 }
15879
15880 int
15881 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15882 {
15883   char buf[MSG_SIZ];
15884   int len = strlen(name);
15885   int val;
15886
15887   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15888     (*p) += len + 1;
15889     sscanf(*p, "%d", &val);
15890     *loc = (val != 0);
15891     while (**p && **p != ' ')
15892       (*p)++;
15893     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15894     SendToProgram(buf, cps);
15895     return TRUE;
15896   }
15897   return FALSE;
15898 }
15899
15900 int
15901 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15902 {
15903   char buf[MSG_SIZ];
15904   int len = strlen(name);
15905   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15906     (*p) += len + 1;
15907     sscanf(*p, "%d", loc);
15908     while (**p && **p != ' ') (*p)++;
15909     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15910     SendToProgram(buf, cps);
15911     return TRUE;
15912   }
15913   return FALSE;
15914 }
15915
15916 int
15917 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15918 {
15919   char buf[MSG_SIZ];
15920   int len = strlen(name);
15921   if (strncmp((*p), name, len) == 0
15922       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15923     (*p) += len + 2;
15924     sscanf(*p, "%[^\"]", loc);
15925     while (**p && **p != '\"') (*p)++;
15926     if (**p == '\"') (*p)++;
15927     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15928     SendToProgram(buf, cps);
15929     return TRUE;
15930   }
15931   return FALSE;
15932 }
15933
15934 int
15935 ParseOption (Option *opt, ChessProgramState *cps)
15936 // [HGM] options: process the string that defines an engine option, and determine
15937 // name, type, default value, and allowed value range
15938 {
15939         char *p, *q, buf[MSG_SIZ];
15940         int n, min = (-1)<<31, max = 1<<31, def;
15941
15942         if(p = strstr(opt->name, " -spin ")) {
15943             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15944             if(max < min) max = min; // enforce consistency
15945             if(def < min) def = min;
15946             if(def > max) def = max;
15947             opt->value = def;
15948             opt->min = min;
15949             opt->max = max;
15950             opt->type = Spin;
15951         } else if((p = strstr(opt->name, " -slider "))) {
15952             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15953             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15954             if(max < min) max = min; // enforce consistency
15955             if(def < min) def = min;
15956             if(def > max) def = max;
15957             opt->value = def;
15958             opt->min = min;
15959             opt->max = max;
15960             opt->type = Spin; // Slider;
15961         } else if((p = strstr(opt->name, " -string "))) {
15962             opt->textValue = p+9;
15963             opt->type = TextBox;
15964         } else if((p = strstr(opt->name, " -file "))) {
15965             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15966             opt->textValue = p+7;
15967             opt->type = FileName; // FileName;
15968         } else if((p = strstr(opt->name, " -path "))) {
15969             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15970             opt->textValue = p+7;
15971             opt->type = PathName; // PathName;
15972         } else if(p = strstr(opt->name, " -check ")) {
15973             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15974             opt->value = (def != 0);
15975             opt->type = CheckBox;
15976         } else if(p = strstr(opt->name, " -combo ")) {
15977             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15978             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15979             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15980             opt->value = n = 0;
15981             while(q = StrStr(q, " /// ")) {
15982                 n++; *q = 0;    // count choices, and null-terminate each of them
15983                 q += 5;
15984                 if(*q == '*') { // remember default, which is marked with * prefix
15985                     q++;
15986                     opt->value = n;
15987                 }
15988                 cps->comboList[cps->comboCnt++] = q;
15989             }
15990             cps->comboList[cps->comboCnt++] = NULL;
15991             opt->max = n + 1;
15992             opt->type = ComboBox;
15993         } else if(p = strstr(opt->name, " -button")) {
15994             opt->type = Button;
15995         } else if(p = strstr(opt->name, " -save")) {
15996             opt->type = SaveButton;
15997         } else return FALSE;
15998         *p = 0; // terminate option name
15999         // now look if the command-line options define a setting for this engine option.
16000         if(cps->optionSettings && cps->optionSettings[0])
16001             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16002         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16003           snprintf(buf, MSG_SIZ, "option %s", p);
16004                 if(p = strstr(buf, ",")) *p = 0;
16005                 if(q = strchr(buf, '=')) switch(opt->type) {
16006                     case ComboBox:
16007                         for(n=0; n<opt->max; n++)
16008                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16009                         break;
16010                     case TextBox:
16011                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16012                         break;
16013                     case Spin:
16014                     case CheckBox:
16015                         opt->value = atoi(q+1);
16016                     default:
16017                         break;
16018                 }
16019                 strcat(buf, "\n");
16020                 SendToProgram(buf, cps);
16021         }
16022         return TRUE;
16023 }
16024
16025 void
16026 FeatureDone (ChessProgramState *cps, int val)
16027 {
16028   DelayedEventCallback cb = GetDelayedEvent();
16029   if ((cb == InitBackEnd3 && cps == &first) ||
16030       (cb == SettingsMenuIfReady && cps == &second) ||
16031       (cb == LoadEngine) ||
16032       (cb == TwoMachinesEventIfReady)) {
16033     CancelDelayedEvent();
16034     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16035   }
16036   cps->initDone = val;
16037   if(val) cps->reload = FALSE;
16038 }
16039
16040 /* Parse feature command from engine */
16041 void
16042 ParseFeatures (char *args, ChessProgramState *cps)
16043 {
16044   char *p = args;
16045   char *q;
16046   int val;
16047   char buf[MSG_SIZ];
16048
16049   for (;;) {
16050     while (*p == ' ') p++;
16051     if (*p == NULLCHAR) return;
16052
16053     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16054     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16055     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16056     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16057     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16058     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16059     if (BoolFeature(&p, "reuse", &val, cps)) {
16060       /* Engine can disable reuse, but can't enable it if user said no */
16061       if (!val) cps->reuse = FALSE;
16062       continue;
16063     }
16064     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16065     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16066       if (gameMode == TwoMachinesPlay) {
16067         DisplayTwoMachinesTitle();
16068       } else {
16069         DisplayTitle("");
16070       }
16071       continue;
16072     }
16073     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16074     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16075     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16076     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16077     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16078     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16079     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16080     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16081     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16082     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16083     if (IntFeature(&p, "done", &val, cps)) {
16084       FeatureDone(cps, val);
16085       continue;
16086     }
16087     /* Added by Tord: */
16088     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16089     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16090     /* End of additions by Tord */
16091
16092     /* [HGM] added features: */
16093     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16094     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16095     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16096     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16097     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16098     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16099     if (StringFeature(&p, "option", buf, cps)) {
16100         if(cps->reload) continue; // we are reloading because of xreuse
16101         FREE(cps->option[cps->nrOptions].name);
16102         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16103         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16104         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16105           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16106             SendToProgram(buf, cps);
16107             continue;
16108         }
16109         if(cps->nrOptions >= MAX_OPTIONS) {
16110             cps->nrOptions--;
16111             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16112             DisplayError(buf, 0);
16113         }
16114         continue;
16115     }
16116     /* End of additions by HGM */
16117
16118     /* unknown feature: complain and skip */
16119     q = p;
16120     while (*q && *q != '=') q++;
16121     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16122     SendToProgram(buf, cps);
16123     p = q;
16124     if (*p == '=') {
16125       p++;
16126       if (*p == '\"') {
16127         p++;
16128         while (*p && *p != '\"') p++;
16129         if (*p == '\"') p++;
16130       } else {
16131         while (*p && *p != ' ') p++;
16132       }
16133     }
16134   }
16135
16136 }
16137
16138 void
16139 PeriodicUpdatesEvent (int newState)
16140 {
16141     if (newState == appData.periodicUpdates)
16142       return;
16143
16144     appData.periodicUpdates=newState;
16145
16146     /* Display type changes, so update it now */
16147 //    DisplayAnalysis();
16148
16149     /* Get the ball rolling again... */
16150     if (newState) {
16151         AnalysisPeriodicEvent(1);
16152         StartAnalysisClock();
16153     }
16154 }
16155
16156 void
16157 PonderNextMoveEvent (int newState)
16158 {
16159     if (newState == appData.ponderNextMove) return;
16160     if (gameMode == EditPosition) EditPositionDone(TRUE);
16161     if (newState) {
16162         SendToProgram("hard\n", &first);
16163         if (gameMode == TwoMachinesPlay) {
16164             SendToProgram("hard\n", &second);
16165         }
16166     } else {
16167         SendToProgram("easy\n", &first);
16168         thinkOutput[0] = NULLCHAR;
16169         if (gameMode == TwoMachinesPlay) {
16170             SendToProgram("easy\n", &second);
16171         }
16172     }
16173     appData.ponderNextMove = newState;
16174 }
16175
16176 void
16177 NewSettingEvent (int option, int *feature, char *command, int value)
16178 {
16179     char buf[MSG_SIZ];
16180
16181     if (gameMode == EditPosition) EditPositionDone(TRUE);
16182     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16183     if(feature == NULL || *feature) SendToProgram(buf, &first);
16184     if (gameMode == TwoMachinesPlay) {
16185         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16186     }
16187 }
16188
16189 void
16190 ShowThinkingEvent ()
16191 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16192 {
16193     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16194     int newState = appData.showThinking
16195         // [HGM] thinking: other features now need thinking output as well
16196         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16197
16198     if (oldState == newState) return;
16199     oldState = newState;
16200     if (gameMode == EditPosition) EditPositionDone(TRUE);
16201     if (oldState) {
16202         SendToProgram("post\n", &first);
16203         if (gameMode == TwoMachinesPlay) {
16204             SendToProgram("post\n", &second);
16205         }
16206     } else {
16207         SendToProgram("nopost\n", &first);
16208         thinkOutput[0] = NULLCHAR;
16209         if (gameMode == TwoMachinesPlay) {
16210             SendToProgram("nopost\n", &second);
16211         }
16212     }
16213 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16214 }
16215
16216 void
16217 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16218 {
16219   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16220   if (pr == NoProc) return;
16221   AskQuestion(title, question, replyPrefix, pr);
16222 }
16223
16224 void
16225 TypeInEvent (char firstChar)
16226 {
16227     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16228         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16229         gameMode == AnalyzeMode || gameMode == EditGame ||
16230         gameMode == EditPosition || gameMode == IcsExamining ||
16231         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16232         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16233                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16234                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16235         gameMode == Training) PopUpMoveDialog(firstChar);
16236 }
16237
16238 void
16239 TypeInDoneEvent (char *move)
16240 {
16241         Board board;
16242         int n, fromX, fromY, toX, toY;
16243         char promoChar;
16244         ChessMove moveType;
16245
16246         // [HGM] FENedit
16247         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16248                 EditPositionPasteFEN(move);
16249                 return;
16250         }
16251         // [HGM] movenum: allow move number to be typed in any mode
16252         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16253           ToNrEvent(2*n-1);
16254           return;
16255         }
16256         // undocumented kludge: allow command-line option to be typed in!
16257         // (potentially fatal, and does not implement the effect of the option.)
16258         // should only be used for options that are values on which future decisions will be made,
16259         // and definitely not on options that would be used during initialization.
16260         if(strstr(move, "!!! -") == move) {
16261             ParseArgsFromString(move+4);
16262             return;
16263         }
16264
16265       if (gameMode != EditGame && currentMove != forwardMostMove &&
16266         gameMode != Training) {
16267         DisplayMoveError(_("Displayed move is not current"));
16268       } else {
16269         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16270           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16271         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16272         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16273           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16274           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16275         } else {
16276           DisplayMoveError(_("Could not parse move"));
16277         }
16278       }
16279 }
16280
16281 void
16282 DisplayMove (int moveNumber)
16283 {
16284     char message[MSG_SIZ];
16285     char res[MSG_SIZ];
16286     char cpThinkOutput[MSG_SIZ];
16287
16288     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16289
16290     if (moveNumber == forwardMostMove - 1 ||
16291         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16292
16293         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16294
16295         if (strchr(cpThinkOutput, '\n')) {
16296             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16297         }
16298     } else {
16299         *cpThinkOutput = NULLCHAR;
16300     }
16301
16302     /* [AS] Hide thinking from human user */
16303     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16304         *cpThinkOutput = NULLCHAR;
16305         if( thinkOutput[0] != NULLCHAR ) {
16306             int i;
16307
16308             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16309                 cpThinkOutput[i] = '.';
16310             }
16311             cpThinkOutput[i] = NULLCHAR;
16312             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16313         }
16314     }
16315
16316     if (moveNumber == forwardMostMove - 1 &&
16317         gameInfo.resultDetails != NULL) {
16318         if (gameInfo.resultDetails[0] == NULLCHAR) {
16319           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16320         } else {
16321           snprintf(res, MSG_SIZ, " {%s} %s",
16322                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16323         }
16324     } else {
16325         res[0] = NULLCHAR;
16326     }
16327
16328     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16329         DisplayMessage(res, cpThinkOutput);
16330     } else {
16331       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16332                 WhiteOnMove(moveNumber) ? " " : ".. ",
16333                 parseList[moveNumber], res);
16334         DisplayMessage(message, cpThinkOutput);
16335     }
16336 }
16337
16338 void
16339 DisplayComment (int moveNumber, char *text)
16340 {
16341     char title[MSG_SIZ];
16342
16343     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16344       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16345     } else {
16346       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16347               WhiteOnMove(moveNumber) ? " " : ".. ",
16348               parseList[moveNumber]);
16349     }
16350     if (text != NULL && (appData.autoDisplayComment || commentUp))
16351         CommentPopUp(title, text);
16352 }
16353
16354 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16355  * might be busy thinking or pondering.  It can be omitted if your
16356  * gnuchess is configured to stop thinking immediately on any user
16357  * input.  However, that gnuchess feature depends on the FIONREAD
16358  * ioctl, which does not work properly on some flavors of Unix.
16359  */
16360 void
16361 Attention (ChessProgramState *cps)
16362 {
16363 #if ATTENTION
16364     if (!cps->useSigint) return;
16365     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16366     switch (gameMode) {
16367       case MachinePlaysWhite:
16368       case MachinePlaysBlack:
16369       case TwoMachinesPlay:
16370       case IcsPlayingWhite:
16371       case IcsPlayingBlack:
16372       case AnalyzeMode:
16373       case AnalyzeFile:
16374         /* Skip if we know it isn't thinking */
16375         if (!cps->maybeThinking) return;
16376         if (appData.debugMode)
16377           fprintf(debugFP, "Interrupting %s\n", cps->which);
16378         InterruptChildProcess(cps->pr);
16379         cps->maybeThinking = FALSE;
16380         break;
16381       default:
16382         break;
16383     }
16384 #endif /*ATTENTION*/
16385 }
16386
16387 int
16388 CheckFlags ()
16389 {
16390     if (whiteTimeRemaining <= 0) {
16391         if (!whiteFlag) {
16392             whiteFlag = TRUE;
16393             if (appData.icsActive) {
16394                 if (appData.autoCallFlag &&
16395                     gameMode == IcsPlayingBlack && !blackFlag) {
16396                   SendToICS(ics_prefix);
16397                   SendToICS("flag\n");
16398                 }
16399             } else {
16400                 if (blackFlag) {
16401                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16402                 } else {
16403                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16404                     if (appData.autoCallFlag) {
16405                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16406                         return TRUE;
16407                     }
16408                 }
16409             }
16410         }
16411     }
16412     if (blackTimeRemaining <= 0) {
16413         if (!blackFlag) {
16414             blackFlag = TRUE;
16415             if (appData.icsActive) {
16416                 if (appData.autoCallFlag &&
16417                     gameMode == IcsPlayingWhite && !whiteFlag) {
16418                   SendToICS(ics_prefix);
16419                   SendToICS("flag\n");
16420                 }
16421             } else {
16422                 if (whiteFlag) {
16423                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16424                 } else {
16425                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16426                     if (appData.autoCallFlag) {
16427                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16428                         return TRUE;
16429                     }
16430                 }
16431             }
16432         }
16433     }
16434     return FALSE;
16435 }
16436
16437 void
16438 CheckTimeControl ()
16439 {
16440     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16441         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16442
16443     /*
16444      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16445      */
16446     if ( !WhiteOnMove(forwardMostMove) ) {
16447         /* White made time control */
16448         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16449         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16450         /* [HGM] time odds: correct new time quota for time odds! */
16451                                             / WhitePlayer()->timeOdds;
16452         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16453     } else {
16454         lastBlack -= blackTimeRemaining;
16455         /* Black made time control */
16456         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16457                                             / WhitePlayer()->other->timeOdds;
16458         lastWhite = whiteTimeRemaining;
16459     }
16460 }
16461
16462 void
16463 DisplayBothClocks ()
16464 {
16465     int wom = gameMode == EditPosition ?
16466       !blackPlaysFirst : WhiteOnMove(currentMove);
16467     DisplayWhiteClock(whiteTimeRemaining, wom);
16468     DisplayBlackClock(blackTimeRemaining, !wom);
16469 }
16470
16471
16472 /* Timekeeping seems to be a portability nightmare.  I think everyone
16473    has ftime(), but I'm really not sure, so I'm including some ifdefs
16474    to use other calls if you don't.  Clocks will be less accurate if
16475    you have neither ftime nor gettimeofday.
16476 */
16477
16478 /* VS 2008 requires the #include outside of the function */
16479 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16480 #include <sys/timeb.h>
16481 #endif
16482
16483 /* Get the current time as a TimeMark */
16484 void
16485 GetTimeMark (TimeMark *tm)
16486 {
16487 #if HAVE_GETTIMEOFDAY
16488
16489     struct timeval timeVal;
16490     struct timezone timeZone;
16491
16492     gettimeofday(&timeVal, &timeZone);
16493     tm->sec = (long) timeVal.tv_sec;
16494     tm->ms = (int) (timeVal.tv_usec / 1000L);
16495
16496 #else /*!HAVE_GETTIMEOFDAY*/
16497 #if HAVE_FTIME
16498
16499 // include <sys/timeb.h> / moved to just above start of function
16500     struct timeb timeB;
16501
16502     ftime(&timeB);
16503     tm->sec = (long) timeB.time;
16504     tm->ms = (int) timeB.millitm;
16505
16506 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16507     tm->sec = (long) time(NULL);
16508     tm->ms = 0;
16509 #endif
16510 #endif
16511 }
16512
16513 /* Return the difference in milliseconds between two
16514    time marks.  We assume the difference will fit in a long!
16515 */
16516 long
16517 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16518 {
16519     return 1000L*(tm2->sec - tm1->sec) +
16520            (long) (tm2->ms - tm1->ms);
16521 }
16522
16523
16524 /*
16525  * Code to manage the game clocks.
16526  *
16527  * In tournament play, black starts the clock and then white makes a move.
16528  * We give the human user a slight advantage if he is playing white---the
16529  * clocks don't run until he makes his first move, so it takes zero time.
16530  * Also, we don't account for network lag, so we could get out of sync
16531  * with GNU Chess's clock -- but then, referees are always right.
16532  */
16533
16534 static TimeMark tickStartTM;
16535 static long intendedTickLength;
16536
16537 long
16538 NextTickLength (long timeRemaining)
16539 {
16540     long nominalTickLength, nextTickLength;
16541
16542     if (timeRemaining > 0L && timeRemaining <= 10000L)
16543       nominalTickLength = 100L;
16544     else
16545       nominalTickLength = 1000L;
16546     nextTickLength = timeRemaining % nominalTickLength;
16547     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16548
16549     return nextTickLength;
16550 }
16551
16552 /* Adjust clock one minute up or down */
16553 void
16554 AdjustClock (Boolean which, int dir)
16555 {
16556     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16557     if(which) blackTimeRemaining += 60000*dir;
16558     else      whiteTimeRemaining += 60000*dir;
16559     DisplayBothClocks();
16560     adjustedClock = TRUE;
16561 }
16562
16563 /* Stop clocks and reset to a fresh time control */
16564 void
16565 ResetClocks ()
16566 {
16567     (void) StopClockTimer();
16568     if (appData.icsActive) {
16569         whiteTimeRemaining = blackTimeRemaining = 0;
16570     } else if (searchTime) {
16571         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16572         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16573     } else { /* [HGM] correct new time quote for time odds */
16574         whiteTC = blackTC = fullTimeControlString;
16575         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16576         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16577     }
16578     if (whiteFlag || blackFlag) {
16579         DisplayTitle("");
16580         whiteFlag = blackFlag = FALSE;
16581     }
16582     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16583     DisplayBothClocks();
16584     adjustedClock = FALSE;
16585 }
16586
16587 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16588
16589 /* Decrement running clock by amount of time that has passed */
16590 void
16591 DecrementClocks ()
16592 {
16593     long timeRemaining;
16594     long lastTickLength, fudge;
16595     TimeMark now;
16596
16597     if (!appData.clockMode) return;
16598     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16599
16600     GetTimeMark(&now);
16601
16602     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16603
16604     /* Fudge if we woke up a little too soon */
16605     fudge = intendedTickLength - lastTickLength;
16606     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16607
16608     if (WhiteOnMove(forwardMostMove)) {
16609         if(whiteNPS >= 0) lastTickLength = 0;
16610         timeRemaining = whiteTimeRemaining -= lastTickLength;
16611         if(timeRemaining < 0 && !appData.icsActive) {
16612             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16613             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16614                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16615                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16616             }
16617         }
16618         DisplayWhiteClock(whiteTimeRemaining - fudge,
16619                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16620     } else {
16621         if(blackNPS >= 0) lastTickLength = 0;
16622         timeRemaining = blackTimeRemaining -= lastTickLength;
16623         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16624             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16625             if(suddenDeath) {
16626                 blackStartMove = forwardMostMove;
16627                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16628             }
16629         }
16630         DisplayBlackClock(blackTimeRemaining - fudge,
16631                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16632     }
16633     if (CheckFlags()) return;
16634
16635     if(twoBoards) { // count down secondary board's clocks as well
16636         activePartnerTime -= lastTickLength;
16637         partnerUp = 1;
16638         if(activePartner == 'W')
16639             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16640         else
16641             DisplayBlackClock(activePartnerTime, TRUE);
16642         partnerUp = 0;
16643     }
16644
16645     tickStartTM = now;
16646     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16647     StartClockTimer(intendedTickLength);
16648
16649     /* if the time remaining has fallen below the alarm threshold, sound the
16650      * alarm. if the alarm has sounded and (due to a takeback or time control
16651      * with increment) the time remaining has increased to a level above the
16652      * threshold, reset the alarm so it can sound again.
16653      */
16654
16655     if (appData.icsActive && appData.icsAlarm) {
16656
16657         /* make sure we are dealing with the user's clock */
16658         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16659                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16660            )) return;
16661
16662         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16663             alarmSounded = FALSE;
16664         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16665             PlayAlarmSound();
16666             alarmSounded = TRUE;
16667         }
16668     }
16669 }
16670
16671
16672 /* A player has just moved, so stop the previously running
16673    clock and (if in clock mode) start the other one.
16674    We redisplay both clocks in case we're in ICS mode, because
16675    ICS gives us an update to both clocks after every move.
16676    Note that this routine is called *after* forwardMostMove
16677    is updated, so the last fractional tick must be subtracted
16678    from the color that is *not* on move now.
16679 */
16680 void
16681 SwitchClocks (int newMoveNr)
16682 {
16683     long lastTickLength;
16684     TimeMark now;
16685     int flagged = FALSE;
16686
16687     GetTimeMark(&now);
16688
16689     if (StopClockTimer() && appData.clockMode) {
16690         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16691         if (!WhiteOnMove(forwardMostMove)) {
16692             if(blackNPS >= 0) lastTickLength = 0;
16693             blackTimeRemaining -= lastTickLength;
16694            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16695 //         if(pvInfoList[forwardMostMove].time == -1)
16696                  pvInfoList[forwardMostMove].time =               // use GUI time
16697                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16698         } else {
16699            if(whiteNPS >= 0) lastTickLength = 0;
16700            whiteTimeRemaining -= lastTickLength;
16701            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16702 //         if(pvInfoList[forwardMostMove].time == -1)
16703                  pvInfoList[forwardMostMove].time =
16704                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16705         }
16706         flagged = CheckFlags();
16707     }
16708     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16709     CheckTimeControl();
16710
16711     if (flagged || !appData.clockMode) return;
16712
16713     switch (gameMode) {
16714       case MachinePlaysBlack:
16715       case MachinePlaysWhite:
16716       case BeginningOfGame:
16717         if (pausing) return;
16718         break;
16719
16720       case EditGame:
16721       case PlayFromGameFile:
16722       case IcsExamining:
16723         return;
16724
16725       default:
16726         break;
16727     }
16728
16729     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16730         if(WhiteOnMove(forwardMostMove))
16731              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16732         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16733     }
16734
16735     tickStartTM = now;
16736     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16737       whiteTimeRemaining : blackTimeRemaining);
16738     StartClockTimer(intendedTickLength);
16739 }
16740
16741
16742 /* Stop both clocks */
16743 void
16744 StopClocks ()
16745 {
16746     long lastTickLength;
16747     TimeMark now;
16748
16749     if (!StopClockTimer()) return;
16750     if (!appData.clockMode) return;
16751
16752     GetTimeMark(&now);
16753
16754     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16755     if (WhiteOnMove(forwardMostMove)) {
16756         if(whiteNPS >= 0) lastTickLength = 0;
16757         whiteTimeRemaining -= lastTickLength;
16758         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16759     } else {
16760         if(blackNPS >= 0) lastTickLength = 0;
16761         blackTimeRemaining -= lastTickLength;
16762         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16763     }
16764     CheckFlags();
16765 }
16766
16767 /* Start clock of player on move.  Time may have been reset, so
16768    if clock is already running, stop and restart it. */
16769 void
16770 StartClocks ()
16771 {
16772     (void) StopClockTimer(); /* in case it was running already */
16773     DisplayBothClocks();
16774     if (CheckFlags()) return;
16775
16776     if (!appData.clockMode) return;
16777     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16778
16779     GetTimeMark(&tickStartTM);
16780     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16781       whiteTimeRemaining : blackTimeRemaining);
16782
16783    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16784     whiteNPS = blackNPS = -1;
16785     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16786        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16787         whiteNPS = first.nps;
16788     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16789        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16790         blackNPS = first.nps;
16791     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16792         whiteNPS = second.nps;
16793     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16794         blackNPS = second.nps;
16795     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16796
16797     StartClockTimer(intendedTickLength);
16798 }
16799
16800 char *
16801 TimeString (long ms)
16802 {
16803     long second, minute, hour, day;
16804     char *sign = "";
16805     static char buf[32];
16806
16807     if (ms > 0 && ms <= 9900) {
16808       /* convert milliseconds to tenths, rounding up */
16809       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16810
16811       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16812       return buf;
16813     }
16814
16815     /* convert milliseconds to seconds, rounding up */
16816     /* use floating point to avoid strangeness of integer division
16817        with negative dividends on many machines */
16818     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16819
16820     if (second < 0) {
16821         sign = "-";
16822         second = -second;
16823     }
16824
16825     day = second / (60 * 60 * 24);
16826     second = second % (60 * 60 * 24);
16827     hour = second / (60 * 60);
16828     second = second % (60 * 60);
16829     minute = second / 60;
16830     second = second % 60;
16831
16832     if (day > 0)
16833       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16834               sign, day, hour, minute, second);
16835     else if (hour > 0)
16836       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16837     else
16838       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16839
16840     return buf;
16841 }
16842
16843
16844 /*
16845  * This is necessary because some C libraries aren't ANSI C compliant yet.
16846  */
16847 char *
16848 StrStr (char *string, char *match)
16849 {
16850     int i, length;
16851
16852     length = strlen(match);
16853
16854     for (i = strlen(string) - length; i >= 0; i--, string++)
16855       if (!strncmp(match, string, length))
16856         return string;
16857
16858     return NULL;
16859 }
16860
16861 char *
16862 StrCaseStr (char *string, char *match)
16863 {
16864     int i, j, length;
16865
16866     length = strlen(match);
16867
16868     for (i = strlen(string) - length; i >= 0; i--, string++) {
16869         for (j = 0; j < length; j++) {
16870             if (ToLower(match[j]) != ToLower(string[j]))
16871               break;
16872         }
16873         if (j == length) return string;
16874     }
16875
16876     return NULL;
16877 }
16878
16879 #ifndef _amigados
16880 int
16881 StrCaseCmp (char *s1, char *s2)
16882 {
16883     char c1, c2;
16884
16885     for (;;) {
16886         c1 = ToLower(*s1++);
16887         c2 = ToLower(*s2++);
16888         if (c1 > c2) return 1;
16889         if (c1 < c2) return -1;
16890         if (c1 == NULLCHAR) return 0;
16891     }
16892 }
16893
16894
16895 int
16896 ToLower (int c)
16897 {
16898     return isupper(c) ? tolower(c) : c;
16899 }
16900
16901
16902 int
16903 ToUpper (int c)
16904 {
16905     return islower(c) ? toupper(c) : c;
16906 }
16907 #endif /* !_amigados    */
16908
16909 char *
16910 StrSave (char *s)
16911 {
16912   char *ret;
16913
16914   if ((ret = (char *) malloc(strlen(s) + 1)))
16915     {
16916       safeStrCpy(ret, s, strlen(s)+1);
16917     }
16918   return ret;
16919 }
16920
16921 char *
16922 StrSavePtr (char *s, char **savePtr)
16923 {
16924     if (*savePtr) {
16925         free(*savePtr);
16926     }
16927     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16928       safeStrCpy(*savePtr, s, strlen(s)+1);
16929     }
16930     return(*savePtr);
16931 }
16932
16933 char *
16934 PGNDate ()
16935 {
16936     time_t clock;
16937     struct tm *tm;
16938     char buf[MSG_SIZ];
16939
16940     clock = time((time_t *)NULL);
16941     tm = localtime(&clock);
16942     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16943             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16944     return StrSave(buf);
16945 }
16946
16947
16948 char *
16949 PositionToFEN (int move, char *overrideCastling)
16950 {
16951     int i, j, fromX, fromY, toX, toY;
16952     int whiteToPlay;
16953     char buf[MSG_SIZ];
16954     char *p, *q;
16955     int emptycount;
16956     ChessSquare piece;
16957
16958     whiteToPlay = (gameMode == EditPosition) ?
16959       !blackPlaysFirst : (move % 2 == 0);
16960     p = buf;
16961
16962     /* Piece placement data */
16963     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16964         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16965         emptycount = 0;
16966         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16967             if (boards[move][i][j] == EmptySquare) {
16968                 emptycount++;
16969             } else { ChessSquare piece = boards[move][i][j];
16970                 if (emptycount > 0) {
16971                     if(emptycount<10) /* [HGM] can be >= 10 */
16972                         *p++ = '0' + emptycount;
16973                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16974                     emptycount = 0;
16975                 }
16976                 if(PieceToChar(piece) == '+') {
16977                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16978                     *p++ = '+';
16979                     piece = (ChessSquare)(DEMOTED piece);
16980                 }
16981                 *p++ = PieceToChar(piece);
16982                 if(p[-1] == '~') {
16983                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16984                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16985                     *p++ = '~';
16986                 }
16987             }
16988         }
16989         if (emptycount > 0) {
16990             if(emptycount<10) /* [HGM] can be >= 10 */
16991                 *p++ = '0' + emptycount;
16992             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16993             emptycount = 0;
16994         }
16995         *p++ = '/';
16996     }
16997     *(p - 1) = ' ';
16998
16999     /* [HGM] print Crazyhouse or Shogi holdings */
17000     if( gameInfo.holdingsWidth ) {
17001         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17002         q = p;
17003         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17004             piece = boards[move][i][BOARD_WIDTH-1];
17005             if( piece != EmptySquare )
17006               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17007                   *p++ = PieceToChar(piece);
17008         }
17009         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17010             piece = boards[move][BOARD_HEIGHT-i-1][0];
17011             if( piece != EmptySquare )
17012               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17013                   *p++ = PieceToChar(piece);
17014         }
17015
17016         if( q == p ) *p++ = '-';
17017         *p++ = ']';
17018         *p++ = ' ';
17019     }
17020
17021     /* Active color */
17022     *p++ = whiteToPlay ? 'w' : 'b';
17023     *p++ = ' ';
17024
17025   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17026     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17027   } else {
17028   if(nrCastlingRights) {
17029      q = p;
17030      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17031        /* [HGM] write directly from rights */
17032            if(boards[move][CASTLING][2] != NoRights &&
17033               boards[move][CASTLING][0] != NoRights   )
17034                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17035            if(boards[move][CASTLING][2] != NoRights &&
17036               boards[move][CASTLING][1] != NoRights   )
17037                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17038            if(boards[move][CASTLING][5] != NoRights &&
17039               boards[move][CASTLING][3] != NoRights   )
17040                 *p++ = boards[move][CASTLING][3] + AAA;
17041            if(boards[move][CASTLING][5] != NoRights &&
17042               boards[move][CASTLING][4] != NoRights   )
17043                 *p++ = boards[move][CASTLING][4] + AAA;
17044      } else {
17045
17046         /* [HGM] write true castling rights */
17047         if( nrCastlingRights == 6 ) {
17048             int q, k=0;
17049             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17050                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17051             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17052                  boards[move][CASTLING][2] != NoRights  );
17053             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17054                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17055                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17056                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17057                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17058             }
17059             if(q) *p++ = 'Q';
17060             k = 0;
17061             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17062                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17063             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17064                  boards[move][CASTLING][5] != NoRights  );
17065             if(gameInfo.variant == VariantSChess) {
17066                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17067                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17068                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17069                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17070             }
17071             if(q) *p++ = 'q';
17072         }
17073      }
17074      if (q == p) *p++ = '-'; /* No castling rights */
17075      *p++ = ' ';
17076   }
17077
17078   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17079      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17080     /* En passant target square */
17081     if (move > backwardMostMove) {
17082         fromX = moveList[move - 1][0] - AAA;
17083         fromY = moveList[move - 1][1] - ONE;
17084         toX = moveList[move - 1][2] - AAA;
17085         toY = moveList[move - 1][3] - ONE;
17086         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17087             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17088             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17089             fromX == toX) {
17090             /* 2-square pawn move just happened */
17091             *p++ = toX + AAA;
17092             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17093         } else {
17094             *p++ = '-';
17095         }
17096     } else if(move == backwardMostMove) {
17097         // [HGM] perhaps we should always do it like this, and forget the above?
17098         if((signed char)boards[move][EP_STATUS] >= 0) {
17099             *p++ = boards[move][EP_STATUS] + AAA;
17100             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17101         } else {
17102             *p++ = '-';
17103         }
17104     } else {
17105         *p++ = '-';
17106     }
17107     *p++ = ' ';
17108   }
17109   }
17110
17111     /* [HGM] find reversible plies */
17112     {   int i = 0, j=move;
17113
17114         if (appData.debugMode) { int k;
17115             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17116             for(k=backwardMostMove; k<=forwardMostMove; k++)
17117                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17118
17119         }
17120
17121         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17122         if( j == backwardMostMove ) i += initialRulePlies;
17123         sprintf(p, "%d ", i);
17124         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17125     }
17126     /* Fullmove number */
17127     sprintf(p, "%d", (move / 2) + 1);
17128
17129     return StrSave(buf);
17130 }
17131
17132 Boolean
17133 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17134 {
17135     int i, j;
17136     char *p, c;
17137     int emptycount, virgin[BOARD_FILES];
17138     ChessSquare piece;
17139
17140     p = fen;
17141
17142     /* [HGM] by default clear Crazyhouse holdings, if present */
17143     if(gameInfo.holdingsWidth) {
17144        for(i=0; i<BOARD_HEIGHT; i++) {
17145            board[i][0]             = EmptySquare; /* black holdings */
17146            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17147            board[i][1]             = (ChessSquare) 0; /* black counts */
17148            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17149        }
17150     }
17151
17152     /* Piece placement data */
17153     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17154         j = 0;
17155         for (;;) {
17156             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17157                 if (*p == '/') p++;
17158                 emptycount = gameInfo.boardWidth - j;
17159                 while (emptycount--)
17160                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17161                 break;
17162 #if(BOARD_FILES >= 10)
17163             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17164                 p++; emptycount=10;
17165                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17166                 while (emptycount--)
17167                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17168 #endif
17169             } else if (isdigit(*p)) {
17170                 emptycount = *p++ - '0';
17171                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17172                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17173                 while (emptycount--)
17174                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17175             } else if (*p == '+' || isalpha(*p)) {
17176                 if (j >= gameInfo.boardWidth) return FALSE;
17177                 if(*p=='+') {
17178                     piece = CharToPiece(*++p);
17179                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17180                     piece = (ChessSquare) (PROMOTED piece ); p++;
17181                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17182                 } else piece = CharToPiece(*p++);
17183
17184                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17185                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17186                     piece = (ChessSquare) (PROMOTED piece);
17187                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17188                     p++;
17189                 }
17190                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17191             } else {
17192                 return FALSE;
17193             }
17194         }
17195     }
17196     while (*p == '/' || *p == ' ') p++;
17197
17198     /* [HGM] look for Crazyhouse holdings here */
17199     while(*p==' ') p++;
17200     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17201         if(*p == '[') p++;
17202         if(*p == '-' ) p++; /* empty holdings */ else {
17203             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17204             /* if we would allow FEN reading to set board size, we would   */
17205             /* have to add holdings and shift the board read so far here   */
17206             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17207                 p++;
17208                 if((int) piece >= (int) BlackPawn ) {
17209                     i = (int)piece - (int)BlackPawn;
17210                     i = PieceToNumber((ChessSquare)i);
17211                     if( i >= gameInfo.holdingsSize ) return FALSE;
17212                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17213                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17214                 } else {
17215                     i = (int)piece - (int)WhitePawn;
17216                     i = PieceToNumber((ChessSquare)i);
17217                     if( i >= gameInfo.holdingsSize ) return FALSE;
17218                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17219                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17220                 }
17221             }
17222         }
17223         if(*p == ']') p++;
17224     }
17225
17226     while(*p == ' ') p++;
17227
17228     /* Active color */
17229     c = *p++;
17230     if(appData.colorNickNames) {
17231       if( c == appData.colorNickNames[0] ) c = 'w'; else
17232       if( c == appData.colorNickNames[1] ) c = 'b';
17233     }
17234     switch (c) {
17235       case 'w':
17236         *blackPlaysFirst = FALSE;
17237         break;
17238       case 'b':
17239         *blackPlaysFirst = TRUE;
17240         break;
17241       default:
17242         return FALSE;
17243     }
17244
17245     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17246     /* return the extra info in global variiables             */
17247
17248     /* set defaults in case FEN is incomplete */
17249     board[EP_STATUS] = EP_UNKNOWN;
17250     for(i=0; i<nrCastlingRights; i++ ) {
17251         board[CASTLING][i] =
17252             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17253     }   /* assume possible unless obviously impossible */
17254     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17255     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17256     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17257                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17258     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17259     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17260     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17261                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17262     FENrulePlies = 0;
17263
17264     while(*p==' ') p++;
17265     if(nrCastlingRights) {
17266       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17267       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17268           /* castling indicator present, so default becomes no castlings */
17269           for(i=0; i<nrCastlingRights; i++ ) {
17270                  board[CASTLING][i] = NoRights;
17271           }
17272       }
17273       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17274              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17275              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17276              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17277         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17278
17279         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17280             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17281             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17282         }
17283         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17284             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17285         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17286                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17287         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17288                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17289         switch(c) {
17290           case'K':
17291               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17292               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17293               board[CASTLING][2] = whiteKingFile;
17294               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17295               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17296               break;
17297           case'Q':
17298               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17299               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17300               board[CASTLING][2] = whiteKingFile;
17301               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17302               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17303               break;
17304           case'k':
17305               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17306               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17307               board[CASTLING][5] = blackKingFile;
17308               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17309               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17310               break;
17311           case'q':
17312               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17313               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17314               board[CASTLING][5] = blackKingFile;
17315               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17316               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17317           case '-':
17318               break;
17319           default: /* FRC castlings */
17320               if(c >= 'a') { /* black rights */
17321                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17322                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17323                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17324                   if(i == BOARD_RGHT) break;
17325                   board[CASTLING][5] = i;
17326                   c -= AAA;
17327                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17328                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17329                   if(c > i)
17330                       board[CASTLING][3] = c;
17331                   else
17332                       board[CASTLING][4] = c;
17333               } else { /* white rights */
17334                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17335                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17336                     if(board[0][i] == WhiteKing) break;
17337                   if(i == BOARD_RGHT) break;
17338                   board[CASTLING][2] = i;
17339                   c -= AAA - 'a' + 'A';
17340                   if(board[0][c] >= WhiteKing) break;
17341                   if(c > i)
17342                       board[CASTLING][0] = c;
17343                   else
17344                       board[CASTLING][1] = c;
17345               }
17346         }
17347       }
17348       for(i=0; i<nrCastlingRights; i++)
17349         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17350       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17351     if (appData.debugMode) {
17352         fprintf(debugFP, "FEN castling rights:");
17353         for(i=0; i<nrCastlingRights; i++)
17354         fprintf(debugFP, " %d", board[CASTLING][i]);
17355         fprintf(debugFP, "\n");
17356     }
17357
17358       while(*p==' ') p++;
17359     }
17360
17361     /* read e.p. field in games that know e.p. capture */
17362     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17363        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17364       if(*p=='-') {
17365         p++; board[EP_STATUS] = EP_NONE;
17366       } else {
17367          char c = *p++ - AAA;
17368
17369          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17370          if(*p >= '0' && *p <='9') p++;
17371          board[EP_STATUS] = c;
17372       }
17373     }
17374
17375
17376     if(sscanf(p, "%d", &i) == 1) {
17377         FENrulePlies = i; /* 50-move ply counter */
17378         /* (The move number is still ignored)    */
17379     }
17380
17381     return TRUE;
17382 }
17383
17384 void
17385 EditPositionPasteFEN (char *fen)
17386 {
17387   if (fen != NULL) {
17388     Board initial_position;
17389
17390     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17391       DisplayError(_("Bad FEN position in clipboard"), 0);
17392       return ;
17393     } else {
17394       int savedBlackPlaysFirst = blackPlaysFirst;
17395       EditPositionEvent();
17396       blackPlaysFirst = savedBlackPlaysFirst;
17397       CopyBoard(boards[0], initial_position);
17398       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17399       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17400       DisplayBothClocks();
17401       DrawPosition(FALSE, boards[currentMove]);
17402     }
17403   }
17404 }
17405
17406 static char cseq[12] = "\\   ";
17407
17408 Boolean
17409 set_cont_sequence (char *new_seq)
17410 {
17411     int len;
17412     Boolean ret;
17413
17414     // handle bad attempts to set the sequence
17415         if (!new_seq)
17416                 return 0; // acceptable error - no debug
17417
17418     len = strlen(new_seq);
17419     ret = (len > 0) && (len < sizeof(cseq));
17420     if (ret)
17421       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17422     else if (appData.debugMode)
17423       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17424     return ret;
17425 }
17426
17427 /*
17428     reformat a source message so words don't cross the width boundary.  internal
17429     newlines are not removed.  returns the wrapped size (no null character unless
17430     included in source message).  If dest is NULL, only calculate the size required
17431     for the dest buffer.  lp argument indicats line position upon entry, and it's
17432     passed back upon exit.
17433 */
17434 int
17435 wrap (char *dest, char *src, int count, int width, int *lp)
17436 {
17437     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17438
17439     cseq_len = strlen(cseq);
17440     old_line = line = *lp;
17441     ansi = len = clen = 0;
17442
17443     for (i=0; i < count; i++)
17444     {
17445         if (src[i] == '\033')
17446             ansi = 1;
17447
17448         // if we hit the width, back up
17449         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17450         {
17451             // store i & len in case the word is too long
17452             old_i = i, old_len = len;
17453
17454             // find the end of the last word
17455             while (i && src[i] != ' ' && src[i] != '\n')
17456             {
17457                 i--;
17458                 len--;
17459             }
17460
17461             // word too long?  restore i & len before splitting it
17462             if ((old_i-i+clen) >= width)
17463             {
17464                 i = old_i;
17465                 len = old_len;
17466             }
17467
17468             // extra space?
17469             if (i && src[i-1] == ' ')
17470                 len--;
17471
17472             if (src[i] != ' ' && src[i] != '\n')
17473             {
17474                 i--;
17475                 if (len)
17476                     len--;
17477             }
17478
17479             // now append the newline and continuation sequence
17480             if (dest)
17481                 dest[len] = '\n';
17482             len++;
17483             if (dest)
17484                 strncpy(dest+len, cseq, cseq_len);
17485             len += cseq_len;
17486             line = cseq_len;
17487             clen = cseq_len;
17488             continue;
17489         }
17490
17491         if (dest)
17492             dest[len] = src[i];
17493         len++;
17494         if (!ansi)
17495             line++;
17496         if (src[i] == '\n')
17497             line = 0;
17498         if (src[i] == 'm')
17499             ansi = 0;
17500     }
17501     if (dest && appData.debugMode)
17502     {
17503         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17504             count, width, line, len, *lp);
17505         show_bytes(debugFP, src, count);
17506         fprintf(debugFP, "\ndest: ");
17507         show_bytes(debugFP, dest, len);
17508         fprintf(debugFP, "\n");
17509     }
17510     *lp = dest ? line : old_line;
17511
17512     return len;
17513 }
17514
17515 // [HGM] vari: routines for shelving variations
17516 Boolean modeRestore = FALSE;
17517
17518 void
17519 PushInner (int firstMove, int lastMove)
17520 {
17521         int i, j, nrMoves = lastMove - firstMove;
17522
17523         // push current tail of game on stack
17524         savedResult[storedGames] = gameInfo.result;
17525         savedDetails[storedGames] = gameInfo.resultDetails;
17526         gameInfo.resultDetails = NULL;
17527         savedFirst[storedGames] = firstMove;
17528         savedLast [storedGames] = lastMove;
17529         savedFramePtr[storedGames] = framePtr;
17530         framePtr -= nrMoves; // reserve space for the boards
17531         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17532             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17533             for(j=0; j<MOVE_LEN; j++)
17534                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17535             for(j=0; j<2*MOVE_LEN; j++)
17536                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17537             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17538             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17539             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17540             pvInfoList[firstMove+i-1].depth = 0;
17541             commentList[framePtr+i] = commentList[firstMove+i];
17542             commentList[firstMove+i] = NULL;
17543         }
17544
17545         storedGames++;
17546         forwardMostMove = firstMove; // truncate game so we can start variation
17547 }
17548
17549 void
17550 PushTail (int firstMove, int lastMove)
17551 {
17552         if(appData.icsActive) { // only in local mode
17553                 forwardMostMove = currentMove; // mimic old ICS behavior
17554                 return;
17555         }
17556         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17557
17558         PushInner(firstMove, lastMove);
17559         if(storedGames == 1) GreyRevert(FALSE);
17560         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17561 }
17562
17563 void
17564 PopInner (Boolean annotate)
17565 {
17566         int i, j, nrMoves;
17567         char buf[8000], moveBuf[20];
17568
17569         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17570         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17571         nrMoves = savedLast[storedGames] - currentMove;
17572         if(annotate) {
17573                 int cnt = 10;
17574                 if(!WhiteOnMove(currentMove))
17575                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17576                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17577                 for(i=currentMove; i<forwardMostMove; i++) {
17578                         if(WhiteOnMove(i))
17579                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17580                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17581                         strcat(buf, moveBuf);
17582                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17583                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17584                 }
17585                 strcat(buf, ")");
17586         }
17587         for(i=1; i<=nrMoves; i++) { // copy last variation back
17588             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17589             for(j=0; j<MOVE_LEN; j++)
17590                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17591             for(j=0; j<2*MOVE_LEN; j++)
17592                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17593             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17594             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17595             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17596             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17597             commentList[currentMove+i] = commentList[framePtr+i];
17598             commentList[framePtr+i] = NULL;
17599         }
17600         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17601         framePtr = savedFramePtr[storedGames];
17602         gameInfo.result = savedResult[storedGames];
17603         if(gameInfo.resultDetails != NULL) {
17604             free(gameInfo.resultDetails);
17605       }
17606         gameInfo.resultDetails = savedDetails[storedGames];
17607         forwardMostMove = currentMove + nrMoves;
17608 }
17609
17610 Boolean
17611 PopTail (Boolean annotate)
17612 {
17613         if(appData.icsActive) return FALSE; // only in local mode
17614         if(!storedGames) return FALSE; // sanity
17615         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17616
17617         PopInner(annotate);
17618         if(currentMove < forwardMostMove) ForwardEvent(); else
17619         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17620
17621         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17622         return TRUE;
17623 }
17624
17625 void
17626 CleanupTail ()
17627 {       // remove all shelved variations
17628         int i;
17629         for(i=0; i<storedGames; i++) {
17630             if(savedDetails[i])
17631                 free(savedDetails[i]);
17632             savedDetails[i] = NULL;
17633         }
17634         for(i=framePtr; i<MAX_MOVES; i++) {
17635                 if(commentList[i]) free(commentList[i]);
17636                 commentList[i] = NULL;
17637         }
17638         framePtr = MAX_MOVES-1;
17639         storedGames = 0;
17640 }
17641
17642 void
17643 LoadVariation (int index, char *text)
17644 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17645         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17646         int level = 0, move;
17647
17648         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17649         // first find outermost bracketing variation
17650         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17651             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17652                 if(*p == '{') wait = '}'; else
17653                 if(*p == '[') wait = ']'; else
17654                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17655                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17656             }
17657             if(*p == wait) wait = NULLCHAR; // closing ]} found
17658             p++;
17659         }
17660         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17661         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17662         end[1] = NULLCHAR; // clip off comment beyond variation
17663         ToNrEvent(currentMove-1);
17664         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17665         // kludge: use ParsePV() to append variation to game
17666         move = currentMove;
17667         ParsePV(start, TRUE, TRUE);
17668         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17669         ClearPremoveHighlights();
17670         CommentPopDown();
17671         ToNrEvent(currentMove+1);
17672 }
17673
17674 void
17675 LoadTheme ()
17676 {
17677     char *p, *q, buf[MSG_SIZ];
17678     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17679         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17680         ParseArgsFromString(buf);
17681         ActivateTheme(TRUE); // also redo colors
17682         return;
17683     }
17684     p = nickName;
17685     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17686     {
17687         int len;
17688         q = appData.themeNames;
17689         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17690       if(appData.useBitmaps) {
17691         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17692                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17693                 appData.liteBackTextureMode,
17694                 appData.darkBackTextureMode );
17695       } else {
17696         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17697                 Col2Text(2),   // lightSquareColor
17698                 Col2Text(3) ); // darkSquareColor
17699       }
17700       if(appData.useBorder) {
17701         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17702                 appData.border);
17703       } else {
17704         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17705       }
17706       if(appData.useFont) {
17707         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17708                 appData.renderPiecesWithFont,
17709                 appData.fontToPieceTable,
17710                 Col2Text(9),    // appData.fontBackColorWhite
17711                 Col2Text(10) ); // appData.fontForeColorBlack
17712       } else {
17713         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17714                 appData.pieceDirectory);
17715         if(!appData.pieceDirectory[0])
17716           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17717                 Col2Text(0),   // whitePieceColor
17718                 Col2Text(1) ); // blackPieceColor
17719       }
17720       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17721                 Col2Text(4),   // highlightSquareColor
17722                 Col2Text(5) ); // premoveHighlightColor
17723         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17724         if(insert != q) insert[-1] = NULLCHAR;
17725         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17726         if(q)   free(q);
17727     }
17728     ActivateTheme(FALSE);
17729 }