Prefer pause mode on pondering engine over 'easy'
[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 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
790     /* New features added by Tord: */
791     cps->useFEN960 = FALSE;
792     cps->useOOCastle = TRUE;
793     /* End of new features added by Tord. */
794     cps->fenOverride  = appData.fenOverride[n];
795
796     /* [HGM] time odds: set factor for each machine */
797     cps->timeOdds  = appData.timeOdds[n];
798
799     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
800     cps->accumulateTC = appData.accumulateTC[n];
801     cps->maxNrOfSessions = 1;
802
803     /* [HGM] debug */
804     cps->debug = FALSE;
805
806     cps->supportsNPS = UNKNOWN;
807     cps->memSize = FALSE;
808     cps->maxCores = FALSE;
809     cps->egtFormats[0] = NULLCHAR;
810
811     /* [HGM] options */
812     cps->optionSettings  = appData.engOptions[n];
813
814     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
815     cps->isUCI = appData.isUCI[n]; /* [AS] */
816     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
817
818     if (appData.protocolVersion[n] > PROTOVER
819         || appData.protocolVersion[n] < 1)
820       {
821         char buf[MSG_SIZ];
822         int len;
823
824         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
825                        appData.protocolVersion[n]);
826         if( (len >= MSG_SIZ) && appData.debugMode )
827           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
828
829         DisplayFatalError(buf, 0, 2);
830       }
831     else
832       {
833         cps->protocolVersion = appData.protocolVersion[n];
834       }
835
836     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
837     ParseFeatures(appData.featureDefaults, cps);
838 }
839
840 ChessProgramState *savCps;
841
842 void
843 LoadEngine ()
844 {
845     int i;
846     if(WaitForEngine(savCps, LoadEngine)) return;
847     CommonEngineInit(); // recalculate time odds
848     if(gameInfo.variant != StringToVariant(appData.variant)) {
849         // we changed variant when loading the engine; this forces us to reset
850         Reset(TRUE, savCps != &first);
851         EditGameEvent(); // for consistency with other path, as Reset changes mode
852     }
853     InitChessProgram(savCps, FALSE);
854     SendToProgram("force\n", savCps);
855     DisplayMessage("", "");
856     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
857     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
858     ThawUI();
859     SetGNUMode();
860 }
861
862 void
863 ReplaceEngine (ChessProgramState *cps, int n)
864 {
865     EditGameEvent();
866     UnloadEngine(cps);
867     appData.noChessProgram = FALSE;
868     appData.clockMode = TRUE;
869     InitEngine(cps, n);
870     UpdateLogos(TRUE);
871     if(n) return; // only startup first engine immediately; second can wait
872     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
873     LoadEngine();
874 }
875
876 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
877 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
878
879 static char resetOptions[] = 
880         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
881         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
882         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
883         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
884
885 void
886 FloatToFront(char **list, char *engineLine)
887 {
888     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
889     int i=0;
890     if(appData.recentEngines <= 0) return;
891     TidyProgramName(engineLine, "localhost", tidy+1);
892     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
893     strncpy(buf+1, *list, MSG_SIZ-50);
894     if(p = strstr(buf, tidy)) { // tidy name appears in list
895         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
896         while(*p++ = *++q); // squeeze out
897     }
898     strcat(tidy, buf+1); // put list behind tidy name
899     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
900     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
901     ASSIGN(*list, tidy+1);
902 }
903
904 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
905
906 void
907 Load (ChessProgramState *cps, int i)
908 {
909     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
910     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
911         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
912         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
913         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
914         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
915         appData.firstProtocolVersion = PROTOVER;
916         ParseArgsFromString(buf);
917         SwapEngines(i);
918         ReplaceEngine(cps, i);
919         FloatToFront(&appData.recentEngineList, engineLine);
920         return;
921     }
922     p = engineName;
923     while(q = strchr(p, SLASH)) p = q+1;
924     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
925     if(engineDir[0] != NULLCHAR) {
926         ASSIGN(appData.directory[i], engineDir); p = engineName;
927     } else if(p != engineName) { // derive directory from engine path, when not given
928         p[-1] = 0;
929         ASSIGN(appData.directory[i], engineName);
930         p[-1] = SLASH;
931         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
932     } else { ASSIGN(appData.directory[i], "."); }
933     if(params[0]) {
934         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
935         snprintf(command, MSG_SIZ, "%s %s", p, params);
936         p = command;
937     }
938     ASSIGN(appData.chessProgram[i], p);
939     appData.isUCI[i] = isUCI;
940     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
941     appData.hasOwnBookUCI[i] = hasBook;
942     if(!nickName[0]) useNick = FALSE;
943     if(useNick) ASSIGN(appData.pgnName[i], nickName);
944     if(addToList) {
945         int len;
946         char quote;
947         q = firstChessProgramNames;
948         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
949         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
950         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
951                         quote, p, quote, appData.directory[i], 
952                         useNick ? " -fn \"" : "",
953                         useNick ? nickName : "",
954                         useNick ? "\"" : "",
955                         v1 ? " -firstProtocolVersion 1" : "",
956                         hasBook ? "" : " -fNoOwnBookUCI",
957                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
958                         storeVariant ? " -variant " : "",
959                         storeVariant ? VariantName(gameInfo.variant) : "");
960         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
961         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
962         if(insert != q) insert[-1] = NULLCHAR;
963         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
964         if(q)   free(q);
965         FloatToFront(&appData.recentEngineList, buf);
966     }
967     ReplaceEngine(cps, i);
968 }
969
970 void
971 InitTimeControls ()
972 {
973     int matched, min, sec;
974     /*
975      * Parse timeControl resource
976      */
977     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
978                           appData.movesPerSession)) {
979         char buf[MSG_SIZ];
980         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
981         DisplayFatalError(buf, 0, 2);
982     }
983
984     /*
985      * Parse searchTime resource
986      */
987     if (*appData.searchTime != NULLCHAR) {
988         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
989         if (matched == 1) {
990             searchTime = min * 60;
991         } else if (matched == 2) {
992             searchTime = min * 60 + sec;
993         } else {
994             char buf[MSG_SIZ];
995             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
996             DisplayFatalError(buf, 0, 2);
997         }
998     }
999 }
1000
1001 void
1002 InitBackEnd1 ()
1003 {
1004
1005     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1006     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1007
1008     GetTimeMark(&programStartTime);
1009     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1010     appData.seedBase = random() + (random()<<15);
1011     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1012
1013     ClearProgramStats();
1014     programStats.ok_to_send = 1;
1015     programStats.seen_stat = 0;
1016
1017     /*
1018      * Initialize game list
1019      */
1020     ListNew(&gameList);
1021
1022
1023     /*
1024      * Internet chess server status
1025      */
1026     if (appData.icsActive) {
1027         appData.matchMode = FALSE;
1028         appData.matchGames = 0;
1029 #if ZIPPY
1030         appData.noChessProgram = !appData.zippyPlay;
1031 #else
1032         appData.zippyPlay = FALSE;
1033         appData.zippyTalk = FALSE;
1034         appData.noChessProgram = TRUE;
1035 #endif
1036         if (*appData.icsHelper != NULLCHAR) {
1037             appData.useTelnet = TRUE;
1038             appData.telnetProgram = appData.icsHelper;
1039         }
1040     } else {
1041         appData.zippyTalk = appData.zippyPlay = FALSE;
1042     }
1043
1044     /* [AS] Initialize pv info list [HGM] and game state */
1045     {
1046         int i, j;
1047
1048         for( i=0; i<=framePtr; i++ ) {
1049             pvInfoList[i].depth = -1;
1050             boards[i][EP_STATUS] = EP_NONE;
1051             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1052         }
1053     }
1054
1055     InitTimeControls();
1056
1057     /* [AS] Adjudication threshold */
1058     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1059
1060     InitEngine(&first, 0);
1061     InitEngine(&second, 1);
1062     CommonEngineInit();
1063
1064     pairing.which = "pairing"; // pairing engine
1065     pairing.pr = NoProc;
1066     pairing.isr = NULL;
1067     pairing.program = appData.pairingEngine;
1068     pairing.host = "localhost";
1069     pairing.dir = ".";
1070
1071     if (appData.icsActive) {
1072         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1073     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1074         appData.clockMode = FALSE;
1075         first.sendTime = second.sendTime = 0;
1076     }
1077
1078 #if ZIPPY
1079     /* Override some settings from environment variables, for backward
1080        compatibility.  Unfortunately it's not feasible to have the env
1081        vars just set defaults, at least in xboard.  Ugh.
1082     */
1083     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1084       ZippyInit();
1085     }
1086 #endif
1087
1088     if (!appData.icsActive) {
1089       char buf[MSG_SIZ];
1090       int len;
1091
1092       /* Check for variants that are supported only in ICS mode,
1093          or not at all.  Some that are accepted here nevertheless
1094          have bugs; see comments below.
1095       */
1096       VariantClass variant = StringToVariant(appData.variant);
1097       switch (variant) {
1098       case VariantBughouse:     /* need four players and two boards */
1099       case VariantKriegspiel:   /* need to hide pieces and move details */
1100         /* case VariantFischeRandom: (Fabien: moved below) */
1101         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1102         if( (len >= MSG_SIZ) && appData.debugMode )
1103           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1104
1105         DisplayFatalError(buf, 0, 2);
1106         return;
1107
1108       case VariantUnknown:
1109       case VariantLoadable:
1110       case Variant29:
1111       case Variant30:
1112       case Variant31:
1113       case Variant32:
1114       case Variant33:
1115       case Variant34:
1116       case Variant35:
1117       case Variant36:
1118       default:
1119         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1120         if( (len >= MSG_SIZ) && appData.debugMode )
1121           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1122
1123         DisplayFatalError(buf, 0, 2);
1124         return;
1125
1126       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1127       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1128       case VariantGothic:     /* [HGM] should work */
1129       case VariantCapablanca: /* [HGM] should work */
1130       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1131       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1132       case VariantKnightmate: /* [HGM] should work */
1133       case VariantCylinder:   /* [HGM] untested */
1134       case VariantFalcon:     /* [HGM] untested */
1135       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1136                                  offboard interposition not understood */
1137       case VariantNormal:     /* definitely works! */
1138       case VariantWildCastle: /* pieces not automatically shuffled */
1139       case VariantNoCastle:   /* pieces not automatically shuffled */
1140       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1141       case VariantLosers:     /* should work except for win condition,
1142                                  and doesn't know captures are mandatory */
1143       case VariantSuicide:    /* should work except for win condition,
1144                                  and doesn't know captures are mandatory */
1145       case VariantGiveaway:   /* should work except for win condition,
1146                                  and doesn't know captures are mandatory */
1147       case VariantTwoKings:   /* should work */
1148       case VariantAtomic:     /* should work except for win condition */
1149       case Variant3Check:     /* should work except for win condition */
1150       case VariantShatranj:   /* should work except for all win conditions */
1151       case VariantMakruk:     /* should work except for draw countdown */
1152       case VariantBerolina:   /* might work if TestLegality is off */
1153       case VariantCapaRandom: /* should work */
1154       case VariantJanus:      /* should work */
1155       case VariantSuper:      /* experimental */
1156       case VariantGreat:      /* experimental, requires legality testing to be off */
1157       case VariantSChess:     /* S-Chess, should work */
1158       case VariantGrand:      /* should work */
1159       case VariantSpartan:    /* should work */
1160         break;
1161       }
1162     }
1163
1164 }
1165
1166 int
1167 NextIntegerFromString (char ** str, long * value)
1168 {
1169     int result = -1;
1170     char * s = *str;
1171
1172     while( *s == ' ' || *s == '\t' ) {
1173         s++;
1174     }
1175
1176     *value = 0;
1177
1178     if( *s >= '0' && *s <= '9' ) {
1179         while( *s >= '0' && *s <= '9' ) {
1180             *value = *value * 10 + (*s - '0');
1181             s++;
1182         }
1183
1184         result = 0;
1185     }
1186
1187     *str = s;
1188
1189     return result;
1190 }
1191
1192 int
1193 NextTimeControlFromString (char ** str, long * value)
1194 {
1195     long temp;
1196     int result = NextIntegerFromString( str, &temp );
1197
1198     if( result == 0 ) {
1199         *value = temp * 60; /* Minutes */
1200         if( **str == ':' ) {
1201             (*str)++;
1202             result = NextIntegerFromString( str, &temp );
1203             *value += temp; /* Seconds */
1204         }
1205     }
1206
1207     return result;
1208 }
1209
1210 int
1211 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1212 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1213     int result = -1, type = 0; long temp, temp2;
1214
1215     if(**str != ':') return -1; // old params remain in force!
1216     (*str)++;
1217     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1218     if( NextIntegerFromString( str, &temp ) ) return -1;
1219     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1220
1221     if(**str != '/') {
1222         /* time only: incremental or sudden-death time control */
1223         if(**str == '+') { /* increment follows; read it */
1224             (*str)++;
1225             if(**str == '!') type = *(*str)++; // Bronstein TC
1226             if(result = NextIntegerFromString( str, &temp2)) return -1;
1227             *inc = temp2 * 1000;
1228             if(**str == '.') { // read fraction of increment
1229                 char *start = ++(*str);
1230                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1231                 temp2 *= 1000;
1232                 while(start++ < *str) temp2 /= 10;
1233                 *inc += temp2;
1234             }
1235         } else *inc = 0;
1236         *moves = 0; *tc = temp * 1000; *incType = type;
1237         return 0;
1238     }
1239
1240     (*str)++; /* classical time control */
1241     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1242
1243     if(result == 0) {
1244         *moves = temp;
1245         *tc    = temp2 * 1000;
1246         *inc   = 0;
1247         *incType = type;
1248     }
1249     return result;
1250 }
1251
1252 int
1253 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1254 {   /* [HGM] get time to add from the multi-session time-control string */
1255     int incType, moves=1; /* kludge to force reading of first session */
1256     long time, increment;
1257     char *s = tcString;
1258
1259     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1260     do {
1261         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1262         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1263         if(movenr == -1) return time;    /* last move before new session     */
1264         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1265         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1266         if(!moves) return increment;     /* current session is incremental   */
1267         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1268     } while(movenr >= -1);               /* try again for next session       */
1269
1270     return 0; // no new time quota on this move
1271 }
1272
1273 int
1274 ParseTimeControl (char *tc, float ti, int mps)
1275 {
1276   long tc1;
1277   long tc2;
1278   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1279   int min, sec=0;
1280
1281   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1282   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1283       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1284   if(ti > 0) {
1285
1286     if(mps)
1287       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1288     else 
1289       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1290   } else {
1291     if(mps)
1292       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1293     else 
1294       snprintf(buf, MSG_SIZ, ":%s", mytc);
1295   }
1296   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1297   
1298   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1299     return FALSE;
1300   }
1301
1302   if( *tc == '/' ) {
1303     /* Parse second time control */
1304     tc++;
1305
1306     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1307       return FALSE;
1308     }
1309
1310     if( tc2 == 0 ) {
1311       return FALSE;
1312     }
1313
1314     timeControl_2 = tc2 * 1000;
1315   }
1316   else {
1317     timeControl_2 = 0;
1318   }
1319
1320   if( tc1 == 0 ) {
1321     return FALSE;
1322   }
1323
1324   timeControl = tc1 * 1000;
1325
1326   if (ti >= 0) {
1327     timeIncrement = ti * 1000;  /* convert to ms */
1328     movesPerSession = 0;
1329   } else {
1330     timeIncrement = 0;
1331     movesPerSession = mps;
1332   }
1333   return TRUE;
1334 }
1335
1336 void
1337 InitBackEnd2 ()
1338 {
1339     if (appData.debugMode) {
1340         fprintf(debugFP, "%s\n", programVersion);
1341     }
1342     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1343
1344     set_cont_sequence(appData.wrapContSeq);
1345     if (appData.matchGames > 0) {
1346         appData.matchMode = TRUE;
1347     } else if (appData.matchMode) {
1348         appData.matchGames = 1;
1349     }
1350     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1351         appData.matchGames = appData.sameColorGames;
1352     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1353         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1354         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1355     }
1356     Reset(TRUE, FALSE);
1357     if (appData.noChessProgram || first.protocolVersion == 1) {
1358       InitBackEnd3();
1359     } else {
1360       /* kludge: allow timeout for initial "feature" commands */
1361       FreezeUI();
1362       DisplayMessage("", _("Starting chess program"));
1363       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1364     }
1365 }
1366
1367 int
1368 CalculateIndex (int index, int gameNr)
1369 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1370     int res;
1371     if(index > 0) return index; // fixed nmber
1372     if(index == 0) return 1;
1373     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1374     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1375     return res;
1376 }
1377
1378 int
1379 LoadGameOrPosition (int gameNr)
1380 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1381     if (*appData.loadGameFile != NULLCHAR) {
1382         if (!LoadGameFromFile(appData.loadGameFile,
1383                 CalculateIndex(appData.loadGameIndex, gameNr),
1384                               appData.loadGameFile, FALSE)) {
1385             DisplayFatalError(_("Bad game file"), 0, 1);
1386             return 0;
1387         }
1388     } else if (*appData.loadPositionFile != NULLCHAR) {
1389         if (!LoadPositionFromFile(appData.loadPositionFile,
1390                 CalculateIndex(appData.loadPositionIndex, gameNr),
1391                                   appData.loadPositionFile)) {
1392             DisplayFatalError(_("Bad position file"), 0, 1);
1393             return 0;
1394         }
1395     }
1396     return 1;
1397 }
1398
1399 void
1400 ReserveGame (int gameNr, char resChar)
1401 {
1402     FILE *tf = fopen(appData.tourneyFile, "r+");
1403     char *p, *q, c, buf[MSG_SIZ];
1404     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1405     safeStrCpy(buf, lastMsg, MSG_SIZ);
1406     DisplayMessage(_("Pick new game"), "");
1407     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1408     ParseArgsFromFile(tf);
1409     p = q = appData.results;
1410     if(appData.debugMode) {
1411       char *r = appData.participants;
1412       fprintf(debugFP, "results = '%s'\n", p);
1413       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1414       fprintf(debugFP, "\n");
1415     }
1416     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1417     nextGame = q - p;
1418     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1419     safeStrCpy(q, p, strlen(p) + 2);
1420     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1421     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1422     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1423         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1424         q[nextGame] = '*';
1425     }
1426     fseek(tf, -(strlen(p)+4), SEEK_END);
1427     c = fgetc(tf);
1428     if(c != '"') // depending on DOS or Unix line endings we can be one off
1429          fseek(tf, -(strlen(p)+2), SEEK_END);
1430     else fseek(tf, -(strlen(p)+3), SEEK_END);
1431     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1432     DisplayMessage(buf, "");
1433     free(p); appData.results = q;
1434     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1435        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1436       int round = appData.defaultMatchGames * appData.tourneyType;
1437       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1438          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1439         UnloadEngine(&first);  // next game belongs to other pairing;
1440         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1441     }
1442     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1443 }
1444
1445 void
1446 MatchEvent (int mode)
1447 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1448         int dummy;
1449         if(matchMode) { // already in match mode: switch it off
1450             abortMatch = TRUE;
1451             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1452             return;
1453         }
1454 //      if(gameMode != BeginningOfGame) {
1455 //          DisplayError(_("You can only start a match from the initial position."), 0);
1456 //          return;
1457 //      }
1458         abortMatch = FALSE;
1459         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1460         /* Set up machine vs. machine match */
1461         nextGame = 0;
1462         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1463         if(appData.tourneyFile[0]) {
1464             ReserveGame(-1, 0);
1465             if(nextGame > appData.matchGames) {
1466                 char buf[MSG_SIZ];
1467                 if(strchr(appData.results, '*') == NULL) {
1468                     FILE *f;
1469                     appData.tourneyCycles++;
1470                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1471                         fclose(f);
1472                         NextTourneyGame(-1, &dummy);
1473                         ReserveGame(-1, 0);
1474                         if(nextGame <= appData.matchGames) {
1475                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1476                             matchMode = mode;
1477                             ScheduleDelayedEvent(NextMatchGame, 10000);
1478                             return;
1479                         }
1480                     }
1481                 }
1482                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1483                 DisplayError(buf, 0);
1484                 appData.tourneyFile[0] = 0;
1485                 return;
1486             }
1487         } else
1488         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1489             DisplayFatalError(_("Can't have a match with no chess programs"),
1490                               0, 2);
1491             return;
1492         }
1493         matchMode = mode;
1494         matchGame = roundNr = 1;
1495         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1496         NextMatchGame();
1497 }
1498
1499 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1500
1501 void
1502 InitBackEnd3 P((void))
1503 {
1504     GameMode initialMode;
1505     char buf[MSG_SIZ];
1506     int err, len;
1507
1508     InitChessProgram(&first, startedFromSetupPosition);
1509
1510     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1511         free(programVersion);
1512         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1513         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1514         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1515     }
1516
1517     if (appData.icsActive) {
1518 #ifdef WIN32
1519         /* [DM] Make a console window if needed [HGM] merged ifs */
1520         ConsoleCreate();
1521 #endif
1522         err = establish();
1523         if (err != 0)
1524           {
1525             if (*appData.icsCommPort != NULLCHAR)
1526               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1527                              appData.icsCommPort);
1528             else
1529               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1530                         appData.icsHost, appData.icsPort);
1531
1532             if( (len >= MSG_SIZ) && appData.debugMode )
1533               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1534
1535             DisplayFatalError(buf, err, 1);
1536             return;
1537         }
1538         SetICSMode();
1539         telnetISR =
1540           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1541         fromUserISR =
1542           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1543         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1544             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1545     } else if (appData.noChessProgram) {
1546         SetNCPMode();
1547     } else {
1548         SetGNUMode();
1549     }
1550
1551     if (*appData.cmailGameName != NULLCHAR) {
1552         SetCmailMode();
1553         OpenLoopback(&cmailPR);
1554         cmailISR =
1555           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1556     }
1557
1558     ThawUI();
1559     DisplayMessage("", "");
1560     if (StrCaseCmp(appData.initialMode, "") == 0) {
1561       initialMode = BeginningOfGame;
1562       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1563         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1564         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1565         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1566         ModeHighlight();
1567       }
1568     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1569       initialMode = TwoMachinesPlay;
1570     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1571       initialMode = AnalyzeFile;
1572     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1573       initialMode = AnalyzeMode;
1574     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1575       initialMode = MachinePlaysWhite;
1576     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1577       initialMode = MachinePlaysBlack;
1578     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1579       initialMode = EditGame;
1580     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1581       initialMode = EditPosition;
1582     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1583       initialMode = Training;
1584     } else {
1585       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1586       if( (len >= MSG_SIZ) && appData.debugMode )
1587         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1588
1589       DisplayFatalError(buf, 0, 2);
1590       return;
1591     }
1592
1593     if (appData.matchMode) {
1594         if(appData.tourneyFile[0]) { // start tourney from command line
1595             FILE *f;
1596             if(f = fopen(appData.tourneyFile, "r")) {
1597                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1598                 fclose(f);
1599                 appData.clockMode = TRUE;
1600                 SetGNUMode();
1601             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1602         }
1603         MatchEvent(TRUE);
1604     } else if (*appData.cmailGameName != NULLCHAR) {
1605         /* Set up cmail mode */
1606         ReloadCmailMsgEvent(TRUE);
1607     } else {
1608         /* Set up other modes */
1609         if (initialMode == AnalyzeFile) {
1610           if (*appData.loadGameFile == NULLCHAR) {
1611             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1612             return;
1613           }
1614         }
1615         if (*appData.loadGameFile != NULLCHAR) {
1616             (void) LoadGameFromFile(appData.loadGameFile,
1617                                     appData.loadGameIndex,
1618                                     appData.loadGameFile, TRUE);
1619         } else if (*appData.loadPositionFile != NULLCHAR) {
1620             (void) LoadPositionFromFile(appData.loadPositionFile,
1621                                         appData.loadPositionIndex,
1622                                         appData.loadPositionFile);
1623             /* [HGM] try to make self-starting even after FEN load */
1624             /* to allow automatic setup of fairy variants with wtm */
1625             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1626                 gameMode = BeginningOfGame;
1627                 setboardSpoiledMachineBlack = 1;
1628             }
1629             /* [HGM] loadPos: make that every new game uses the setup */
1630             /* from file as long as we do not switch variant          */
1631             if(!blackPlaysFirst) {
1632                 startedFromPositionFile = TRUE;
1633                 CopyBoard(filePosition, boards[0]);
1634             }
1635         }
1636         if (initialMode == AnalyzeMode) {
1637           if (appData.noChessProgram) {
1638             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1639             return;
1640           }
1641           if (appData.icsActive) {
1642             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1643             return;
1644           }
1645           AnalyzeModeEvent();
1646         } else if (initialMode == AnalyzeFile) {
1647           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1648           ShowThinkingEvent();
1649           AnalyzeFileEvent();
1650           AnalysisPeriodicEvent(1);
1651         } else if (initialMode == MachinePlaysWhite) {
1652           if (appData.noChessProgram) {
1653             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1654                               0, 2);
1655             return;
1656           }
1657           if (appData.icsActive) {
1658             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1659                               0, 2);
1660             return;
1661           }
1662           MachineWhiteEvent();
1663         } else if (initialMode == MachinePlaysBlack) {
1664           if (appData.noChessProgram) {
1665             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1666                               0, 2);
1667             return;
1668           }
1669           if (appData.icsActive) {
1670             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1671                               0, 2);
1672             return;
1673           }
1674           MachineBlackEvent();
1675         } else if (initialMode == TwoMachinesPlay) {
1676           if (appData.noChessProgram) {
1677             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1678                               0, 2);
1679             return;
1680           }
1681           if (appData.icsActive) {
1682             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1683                               0, 2);
1684             return;
1685           }
1686           TwoMachinesEvent();
1687         } else if (initialMode == EditGame) {
1688           EditGameEvent();
1689         } else if (initialMode == EditPosition) {
1690           EditPositionEvent();
1691         } else if (initialMode == Training) {
1692           if (*appData.loadGameFile == NULLCHAR) {
1693             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1694             return;
1695           }
1696           TrainingEvent();
1697         }
1698     }
1699 }
1700
1701 void
1702 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1703 {
1704     DisplayBook(current+1);
1705
1706     MoveHistorySet( movelist, first, last, current, pvInfoList );
1707
1708     EvalGraphSet( first, last, current, pvInfoList );
1709
1710     MakeEngineOutputTitle();
1711 }
1712
1713 /*
1714  * Establish will establish a contact to a remote host.port.
1715  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1716  *  used to talk to the host.
1717  * Returns 0 if okay, error code if not.
1718  */
1719 int
1720 establish ()
1721 {
1722     char buf[MSG_SIZ];
1723
1724     if (*appData.icsCommPort != NULLCHAR) {
1725         /* Talk to the host through a serial comm port */
1726         return OpenCommPort(appData.icsCommPort, &icsPR);
1727
1728     } else if (*appData.gateway != NULLCHAR) {
1729         if (*appData.remoteShell == NULLCHAR) {
1730             /* Use the rcmd protocol to run telnet program on a gateway host */
1731             snprintf(buf, sizeof(buf), "%s %s %s",
1732                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1733             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1734
1735         } else {
1736             /* Use the rsh program to run telnet program on a gateway host */
1737             if (*appData.remoteUser == NULLCHAR) {
1738                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1739                         appData.gateway, appData.telnetProgram,
1740                         appData.icsHost, appData.icsPort);
1741             } else {
1742                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1743                         appData.remoteShell, appData.gateway,
1744                         appData.remoteUser, appData.telnetProgram,
1745                         appData.icsHost, appData.icsPort);
1746             }
1747             return StartChildProcess(buf, "", &icsPR);
1748
1749         }
1750     } else if (appData.useTelnet) {
1751         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1752
1753     } else {
1754         /* TCP socket interface differs somewhat between
1755            Unix and NT; handle details in the front end.
1756            */
1757         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1758     }
1759 }
1760
1761 void
1762 EscapeExpand (char *p, char *q)
1763 {       // [HGM] initstring: routine to shape up string arguments
1764         while(*p++ = *q++) if(p[-1] == '\\')
1765             switch(*q++) {
1766                 case 'n': p[-1] = '\n'; break;
1767                 case 'r': p[-1] = '\r'; break;
1768                 case 't': p[-1] = '\t'; break;
1769                 case '\\': p[-1] = '\\'; break;
1770                 case 0: *p = 0; return;
1771                 default: p[-1] = q[-1]; break;
1772             }
1773 }
1774
1775 void
1776 show_bytes (FILE *fp, char *buf, int count)
1777 {
1778     while (count--) {
1779         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1780             fprintf(fp, "\\%03o", *buf & 0xff);
1781         } else {
1782             putc(*buf, fp);
1783         }
1784         buf++;
1785     }
1786     fflush(fp);
1787 }
1788
1789 /* Returns an errno value */
1790 int
1791 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1792 {
1793     char buf[8192], *p, *q, *buflim;
1794     int left, newcount, outcount;
1795
1796     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1797         *appData.gateway != NULLCHAR) {
1798         if (appData.debugMode) {
1799             fprintf(debugFP, ">ICS: ");
1800             show_bytes(debugFP, message, count);
1801             fprintf(debugFP, "\n");
1802         }
1803         return OutputToProcess(pr, message, count, outError);
1804     }
1805
1806     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1807     p = message;
1808     q = buf;
1809     left = count;
1810     newcount = 0;
1811     while (left) {
1812         if (q >= buflim) {
1813             if (appData.debugMode) {
1814                 fprintf(debugFP, ">ICS: ");
1815                 show_bytes(debugFP, buf, newcount);
1816                 fprintf(debugFP, "\n");
1817             }
1818             outcount = OutputToProcess(pr, buf, newcount, outError);
1819             if (outcount < newcount) return -1; /* to be sure */
1820             q = buf;
1821             newcount = 0;
1822         }
1823         if (*p == '\n') {
1824             *q++ = '\r';
1825             newcount++;
1826         } else if (((unsigned char) *p) == TN_IAC) {
1827             *q++ = (char) TN_IAC;
1828             newcount ++;
1829         }
1830         *q++ = *p++;
1831         newcount++;
1832         left--;
1833     }
1834     if (appData.debugMode) {
1835         fprintf(debugFP, ">ICS: ");
1836         show_bytes(debugFP, buf, newcount);
1837         fprintf(debugFP, "\n");
1838     }
1839     outcount = OutputToProcess(pr, buf, newcount, outError);
1840     if (outcount < newcount) return -1; /* to be sure */
1841     return count;
1842 }
1843
1844 void
1845 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1846 {
1847     int outError, outCount;
1848     static int gotEof = 0;
1849     static FILE *ini;
1850
1851     /* Pass data read from player on to ICS */
1852     if (count > 0) {
1853         gotEof = 0;
1854         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1855         if (outCount < count) {
1856             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1857         }
1858         if(have_sent_ICS_logon == 2) {
1859           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1860             fprintf(ini, "%s", message);
1861             have_sent_ICS_logon = 3;
1862           } else
1863             have_sent_ICS_logon = 1;
1864         } else if(have_sent_ICS_logon == 3) {
1865             fprintf(ini, "%s", message);
1866             fclose(ini);
1867           have_sent_ICS_logon = 1;
1868         }
1869     } else if (count < 0) {
1870         RemoveInputSource(isr);
1871         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1872     } else if (gotEof++ > 0) {
1873         RemoveInputSource(isr);
1874         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1875     }
1876 }
1877
1878 void
1879 KeepAlive ()
1880 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1881     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1882     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1883     SendToICS("date\n");
1884     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1885 }
1886
1887 /* added routine for printf style output to ics */
1888 void
1889 ics_printf (char *format, ...)
1890 {
1891     char buffer[MSG_SIZ];
1892     va_list args;
1893
1894     va_start(args, format);
1895     vsnprintf(buffer, sizeof(buffer), format, args);
1896     buffer[sizeof(buffer)-1] = '\0';
1897     SendToICS(buffer);
1898     va_end(args);
1899 }
1900
1901 void
1902 SendToICS (char *s)
1903 {
1904     int count, outCount, outError;
1905
1906     if (icsPR == NoProc) return;
1907
1908     count = strlen(s);
1909     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1910     if (outCount < count) {
1911         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1912     }
1913 }
1914
1915 /* This is used for sending logon scripts to the ICS. Sending
1916    without a delay causes problems when using timestamp on ICC
1917    (at least on my machine). */
1918 void
1919 SendToICSDelayed (char *s, long msdelay)
1920 {
1921     int count, outCount, outError;
1922
1923     if (icsPR == NoProc) return;
1924
1925     count = strlen(s);
1926     if (appData.debugMode) {
1927         fprintf(debugFP, ">ICS: ");
1928         show_bytes(debugFP, s, count);
1929         fprintf(debugFP, "\n");
1930     }
1931     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1932                                       msdelay);
1933     if (outCount < count) {
1934         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1935     }
1936 }
1937
1938
1939 /* Remove all highlighting escape sequences in s
1940    Also deletes any suffix starting with '('
1941    */
1942 char *
1943 StripHighlightAndTitle (char *s)
1944 {
1945     static char retbuf[MSG_SIZ];
1946     char *p = retbuf;
1947
1948     while (*s != NULLCHAR) {
1949         while (*s == '\033') {
1950             while (*s != NULLCHAR && !isalpha(*s)) s++;
1951             if (*s != NULLCHAR) s++;
1952         }
1953         while (*s != NULLCHAR && *s != '\033') {
1954             if (*s == '(' || *s == '[') {
1955                 *p = NULLCHAR;
1956                 return retbuf;
1957             }
1958             *p++ = *s++;
1959         }
1960     }
1961     *p = NULLCHAR;
1962     return retbuf;
1963 }
1964
1965 /* Remove all highlighting escape sequences in s */
1966 char *
1967 StripHighlight (char *s)
1968 {
1969     static char retbuf[MSG_SIZ];
1970     char *p = retbuf;
1971
1972     while (*s != NULLCHAR) {
1973         while (*s == '\033') {
1974             while (*s != NULLCHAR && !isalpha(*s)) s++;
1975             if (*s != NULLCHAR) s++;
1976         }
1977         while (*s != NULLCHAR && *s != '\033') {
1978             *p++ = *s++;
1979         }
1980     }
1981     *p = NULLCHAR;
1982     return retbuf;
1983 }
1984
1985 char *variantNames[] = VARIANT_NAMES;
1986 char *
1987 VariantName (VariantClass v)
1988 {
1989     return variantNames[v];
1990 }
1991
1992
1993 /* Identify a variant from the strings the chess servers use or the
1994    PGN Variant tag names we use. */
1995 VariantClass
1996 StringToVariant (char *e)
1997 {
1998     char *p;
1999     int wnum = -1;
2000     VariantClass v = VariantNormal;
2001     int i, found = FALSE;
2002     char buf[MSG_SIZ];
2003     int len;
2004
2005     if (!e) return v;
2006
2007     /* [HGM] skip over optional board-size prefixes */
2008     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2009         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2010         while( *e++ != '_');
2011     }
2012
2013     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2014         v = VariantNormal;
2015         found = TRUE;
2016     } else
2017     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2018       if (StrCaseStr(e, variantNames[i])) {
2019         v = (VariantClass) i;
2020         found = TRUE;
2021         break;
2022       }
2023     }
2024
2025     if (!found) {
2026       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2027           || StrCaseStr(e, "wild/fr")
2028           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2029         v = VariantFischeRandom;
2030       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2031                  (i = 1, p = StrCaseStr(e, "w"))) {
2032         p += i;
2033         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2034         if (isdigit(*p)) {
2035           wnum = atoi(p);
2036         } else {
2037           wnum = -1;
2038         }
2039         switch (wnum) {
2040         case 0: /* FICS only, actually */
2041         case 1:
2042           /* Castling legal even if K starts on d-file */
2043           v = VariantWildCastle;
2044           break;
2045         case 2:
2046         case 3:
2047         case 4:
2048           /* Castling illegal even if K & R happen to start in
2049              normal positions. */
2050           v = VariantNoCastle;
2051           break;
2052         case 5:
2053         case 7:
2054         case 8:
2055         case 10:
2056         case 11:
2057         case 12:
2058         case 13:
2059         case 14:
2060         case 15:
2061         case 18:
2062         case 19:
2063           /* Castling legal iff K & R start in normal positions */
2064           v = VariantNormal;
2065           break;
2066         case 6:
2067         case 20:
2068         case 21:
2069           /* Special wilds for position setup; unclear what to do here */
2070           v = VariantLoadable;
2071           break;
2072         case 9:
2073           /* Bizarre ICC game */
2074           v = VariantTwoKings;
2075           break;
2076         case 16:
2077           v = VariantKriegspiel;
2078           break;
2079         case 17:
2080           v = VariantLosers;
2081           break;
2082         case 22:
2083           v = VariantFischeRandom;
2084           break;
2085         case 23:
2086           v = VariantCrazyhouse;
2087           break;
2088         case 24:
2089           v = VariantBughouse;
2090           break;
2091         case 25:
2092           v = Variant3Check;
2093           break;
2094         case 26:
2095           /* Not quite the same as FICS suicide! */
2096           v = VariantGiveaway;
2097           break;
2098         case 27:
2099           v = VariantAtomic;
2100           break;
2101         case 28:
2102           v = VariantShatranj;
2103           break;
2104
2105         /* Temporary names for future ICC types.  The name *will* change in
2106            the next xboard/WinBoard release after ICC defines it. */
2107         case 29:
2108           v = Variant29;
2109           break;
2110         case 30:
2111           v = Variant30;
2112           break;
2113         case 31:
2114           v = Variant31;
2115           break;
2116         case 32:
2117           v = Variant32;
2118           break;
2119         case 33:
2120           v = Variant33;
2121           break;
2122         case 34:
2123           v = Variant34;
2124           break;
2125         case 35:
2126           v = Variant35;
2127           break;
2128         case 36:
2129           v = Variant36;
2130           break;
2131         case 37:
2132           v = VariantShogi;
2133           break;
2134         case 38:
2135           v = VariantXiangqi;
2136           break;
2137         case 39:
2138           v = VariantCourier;
2139           break;
2140         case 40:
2141           v = VariantGothic;
2142           break;
2143         case 41:
2144           v = VariantCapablanca;
2145           break;
2146         case 42:
2147           v = VariantKnightmate;
2148           break;
2149         case 43:
2150           v = VariantFairy;
2151           break;
2152         case 44:
2153           v = VariantCylinder;
2154           break;
2155         case 45:
2156           v = VariantFalcon;
2157           break;
2158         case 46:
2159           v = VariantCapaRandom;
2160           break;
2161         case 47:
2162           v = VariantBerolina;
2163           break;
2164         case 48:
2165           v = VariantJanus;
2166           break;
2167         case 49:
2168           v = VariantSuper;
2169           break;
2170         case 50:
2171           v = VariantGreat;
2172           break;
2173         case -1:
2174           /* Found "wild" or "w" in the string but no number;
2175              must assume it's normal chess. */
2176           v = VariantNormal;
2177           break;
2178         default:
2179           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2180           if( (len >= MSG_SIZ) && appData.debugMode )
2181             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2182
2183           DisplayError(buf, 0);
2184           v = VariantUnknown;
2185           break;
2186         }
2187       }
2188     }
2189     if (appData.debugMode) {
2190       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2191               e, wnum, VariantName(v));
2192     }
2193     return v;
2194 }
2195
2196 static int leftover_start = 0, leftover_len = 0;
2197 char star_match[STAR_MATCH_N][MSG_SIZ];
2198
2199 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2200    advance *index beyond it, and set leftover_start to the new value of
2201    *index; else return FALSE.  If pattern contains the character '*', it
2202    matches any sequence of characters not containing '\r', '\n', or the
2203    character following the '*' (if any), and the matched sequence(s) are
2204    copied into star_match.
2205    */
2206 int
2207 looking_at ( char *buf, int *index, char *pattern)
2208 {
2209     char *bufp = &buf[*index], *patternp = pattern;
2210     int star_count = 0;
2211     char *matchp = star_match[0];
2212
2213     for (;;) {
2214         if (*patternp == NULLCHAR) {
2215             *index = leftover_start = bufp - buf;
2216             *matchp = NULLCHAR;
2217             return TRUE;
2218         }
2219         if (*bufp == NULLCHAR) return FALSE;
2220         if (*patternp == '*') {
2221             if (*bufp == *(patternp + 1)) {
2222                 *matchp = NULLCHAR;
2223                 matchp = star_match[++star_count];
2224                 patternp += 2;
2225                 bufp++;
2226                 continue;
2227             } else if (*bufp == '\n' || *bufp == '\r') {
2228                 patternp++;
2229                 if (*patternp == NULLCHAR)
2230                   continue;
2231                 else
2232                   return FALSE;
2233             } else {
2234                 *matchp++ = *bufp++;
2235                 continue;
2236             }
2237         }
2238         if (*patternp != *bufp) return FALSE;
2239         patternp++;
2240         bufp++;
2241     }
2242 }
2243
2244 void
2245 SendToPlayer (char *data, int length)
2246 {
2247     int error, outCount;
2248     outCount = OutputToProcess(NoProc, data, length, &error);
2249     if (outCount < length) {
2250         DisplayFatalError(_("Error writing to display"), error, 1);
2251     }
2252 }
2253
2254 void
2255 PackHolding (char packed[], char *holding)
2256 {
2257     char *p = holding;
2258     char *q = packed;
2259     int runlength = 0;
2260     int curr = 9999;
2261     do {
2262         if (*p == curr) {
2263             runlength++;
2264         } else {
2265             switch (runlength) {
2266               case 0:
2267                 break;
2268               case 1:
2269                 *q++ = curr;
2270                 break;
2271               case 2:
2272                 *q++ = curr;
2273                 *q++ = curr;
2274                 break;
2275               default:
2276                 sprintf(q, "%d", runlength);
2277                 while (*q) q++;
2278                 *q++ = curr;
2279                 break;
2280             }
2281             runlength = 1;
2282             curr = *p;
2283         }
2284     } while (*p++);
2285     *q = NULLCHAR;
2286 }
2287
2288 /* Telnet protocol requests from the front end */
2289 void
2290 TelnetRequest (unsigned char ddww, unsigned char option)
2291 {
2292     unsigned char msg[3];
2293     int outCount, outError;
2294
2295     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2296
2297     if (appData.debugMode) {
2298         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2299         switch (ddww) {
2300           case TN_DO:
2301             ddwwStr = "DO";
2302             break;
2303           case TN_DONT:
2304             ddwwStr = "DONT";
2305             break;
2306           case TN_WILL:
2307             ddwwStr = "WILL";
2308             break;
2309           case TN_WONT:
2310             ddwwStr = "WONT";
2311             break;
2312           default:
2313             ddwwStr = buf1;
2314             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2315             break;
2316         }
2317         switch (option) {
2318           case TN_ECHO:
2319             optionStr = "ECHO";
2320             break;
2321           default:
2322             optionStr = buf2;
2323             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2324             break;
2325         }
2326         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2327     }
2328     msg[0] = TN_IAC;
2329     msg[1] = ddww;
2330     msg[2] = option;
2331     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2332     if (outCount < 3) {
2333         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2334     }
2335 }
2336
2337 void
2338 DoEcho ()
2339 {
2340     if (!appData.icsActive) return;
2341     TelnetRequest(TN_DO, TN_ECHO);
2342 }
2343
2344 void
2345 DontEcho ()
2346 {
2347     if (!appData.icsActive) return;
2348     TelnetRequest(TN_DONT, TN_ECHO);
2349 }
2350
2351 void
2352 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2353 {
2354     /* put the holdings sent to us by the server on the board holdings area */
2355     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2356     char p;
2357     ChessSquare piece;
2358
2359     if(gameInfo.holdingsWidth < 2)  return;
2360     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2361         return; // prevent overwriting by pre-board holdings
2362
2363     if( (int)lowestPiece >= BlackPawn ) {
2364         holdingsColumn = 0;
2365         countsColumn = 1;
2366         holdingsStartRow = BOARD_HEIGHT-1;
2367         direction = -1;
2368     } else {
2369         holdingsColumn = BOARD_WIDTH-1;
2370         countsColumn = BOARD_WIDTH-2;
2371         holdingsStartRow = 0;
2372         direction = 1;
2373     }
2374
2375     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2376         board[i][holdingsColumn] = EmptySquare;
2377         board[i][countsColumn]   = (ChessSquare) 0;
2378     }
2379     while( (p=*holdings++) != NULLCHAR ) {
2380         piece = CharToPiece( ToUpper(p) );
2381         if(piece == EmptySquare) continue;
2382         /*j = (int) piece - (int) WhitePawn;*/
2383         j = PieceToNumber(piece);
2384         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2385         if(j < 0) continue;               /* should not happen */
2386         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2387         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2388         board[holdingsStartRow+j*direction][countsColumn]++;
2389     }
2390 }
2391
2392
2393 void
2394 VariantSwitch (Board board, VariantClass newVariant)
2395 {
2396    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2397    static Board oldBoard;
2398
2399    startedFromPositionFile = FALSE;
2400    if(gameInfo.variant == newVariant) return;
2401
2402    /* [HGM] This routine is called each time an assignment is made to
2403     * gameInfo.variant during a game, to make sure the board sizes
2404     * are set to match the new variant. If that means adding or deleting
2405     * holdings, we shift the playing board accordingly
2406     * This kludge is needed because in ICS observe mode, we get boards
2407     * of an ongoing game without knowing the variant, and learn about the
2408     * latter only later. This can be because of the move list we requested,
2409     * in which case the game history is refilled from the beginning anyway,
2410     * but also when receiving holdings of a crazyhouse game. In the latter
2411     * case we want to add those holdings to the already received position.
2412     */
2413
2414
2415    if (appData.debugMode) {
2416      fprintf(debugFP, "Switch board from %s to %s\n",
2417              VariantName(gameInfo.variant), VariantName(newVariant));
2418      setbuf(debugFP, NULL);
2419    }
2420    shuffleOpenings = 0;       /* [HGM] shuffle */
2421    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2422    switch(newVariant)
2423      {
2424      case VariantShogi:
2425        newWidth = 9;  newHeight = 9;
2426        gameInfo.holdingsSize = 7;
2427      case VariantBughouse:
2428      case VariantCrazyhouse:
2429        newHoldingsWidth = 2; break;
2430      case VariantGreat:
2431        newWidth = 10;
2432      case VariantSuper:
2433        newHoldingsWidth = 2;
2434        gameInfo.holdingsSize = 8;
2435        break;
2436      case VariantGothic:
2437      case VariantCapablanca:
2438      case VariantCapaRandom:
2439        newWidth = 10;
2440      default:
2441        newHoldingsWidth = gameInfo.holdingsSize = 0;
2442      };
2443
2444    if(newWidth  != gameInfo.boardWidth  ||
2445       newHeight != gameInfo.boardHeight ||
2446       newHoldingsWidth != gameInfo.holdingsWidth ) {
2447
2448      /* shift position to new playing area, if needed */
2449      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2450        for(i=0; i<BOARD_HEIGHT; i++)
2451          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2452            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2453              board[i][j];
2454        for(i=0; i<newHeight; i++) {
2455          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2456          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2457        }
2458      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2459        for(i=0; i<BOARD_HEIGHT; i++)
2460          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2461            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2462              board[i][j];
2463      }
2464      board[HOLDINGS_SET] = 0;
2465      gameInfo.boardWidth  = newWidth;
2466      gameInfo.boardHeight = newHeight;
2467      gameInfo.holdingsWidth = newHoldingsWidth;
2468      gameInfo.variant = newVariant;
2469      InitDrawingSizes(-2, 0);
2470    } else gameInfo.variant = newVariant;
2471    CopyBoard(oldBoard, board);   // remember correctly formatted board
2472      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2473    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2474 }
2475
2476 static int loggedOn = FALSE;
2477
2478 /*-- Game start info cache: --*/
2479 int gs_gamenum;
2480 char gs_kind[MSG_SIZ];
2481 static char player1Name[128] = "";
2482 static char player2Name[128] = "";
2483 static char cont_seq[] = "\n\\   ";
2484 static int player1Rating = -1;
2485 static int player2Rating = -1;
2486 /*----------------------------*/
2487
2488 ColorClass curColor = ColorNormal;
2489 int suppressKibitz = 0;
2490
2491 // [HGM] seekgraph
2492 Boolean soughtPending = FALSE;
2493 Boolean seekGraphUp;
2494 #define MAX_SEEK_ADS 200
2495 #define SQUARE 0x80
2496 char *seekAdList[MAX_SEEK_ADS];
2497 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2498 float tcList[MAX_SEEK_ADS];
2499 char colorList[MAX_SEEK_ADS];
2500 int nrOfSeekAds = 0;
2501 int minRating = 1010, maxRating = 2800;
2502 int hMargin = 10, vMargin = 20, h, w;
2503 extern int squareSize, lineGap;
2504
2505 void
2506 PlotSeekAd (int i)
2507 {
2508         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2509         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2510         if(r < minRating+100 && r >=0 ) r = minRating+100;
2511         if(r > maxRating) r = maxRating;
2512         if(tc < 1.f) tc = 1.f;
2513         if(tc > 95.f) tc = 95.f;
2514         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2515         y = ((double)r - minRating)/(maxRating - minRating)
2516             * (h-vMargin-squareSize/8-1) + vMargin;
2517         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2518         if(strstr(seekAdList[i], " u ")) color = 1;
2519         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2520            !strstr(seekAdList[i], "bullet") &&
2521            !strstr(seekAdList[i], "blitz") &&
2522            !strstr(seekAdList[i], "standard") ) color = 2;
2523         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2524         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2525 }
2526
2527 void
2528 PlotSingleSeekAd (int i)
2529 {
2530         PlotSeekAd(i);
2531 }
2532
2533 void
2534 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2535 {
2536         char buf[MSG_SIZ], *ext = "";
2537         VariantClass v = StringToVariant(type);
2538         if(strstr(type, "wild")) {
2539             ext = type + 4; // append wild number
2540             if(v == VariantFischeRandom) type = "chess960"; else
2541             if(v == VariantLoadable) type = "setup"; else
2542             type = VariantName(v);
2543         }
2544         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2545         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2546             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2547             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2548             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2549             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2550             seekNrList[nrOfSeekAds] = nr;
2551             zList[nrOfSeekAds] = 0;
2552             seekAdList[nrOfSeekAds++] = StrSave(buf);
2553             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2554         }
2555 }
2556
2557 void
2558 EraseSeekDot (int i)
2559 {
2560     int x = xList[i], y = yList[i], d=squareSize/4, k;
2561     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2562     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2563     // now replot every dot that overlapped
2564     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2565         int xx = xList[k], yy = yList[k];
2566         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2567             DrawSeekDot(xx, yy, colorList[k]);
2568     }
2569 }
2570
2571 void
2572 RemoveSeekAd (int nr)
2573 {
2574         int i;
2575         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2576             EraseSeekDot(i);
2577             if(seekAdList[i]) free(seekAdList[i]);
2578             seekAdList[i] = seekAdList[--nrOfSeekAds];
2579             seekNrList[i] = seekNrList[nrOfSeekAds];
2580             ratingList[i] = ratingList[nrOfSeekAds];
2581             colorList[i]  = colorList[nrOfSeekAds];
2582             tcList[i] = tcList[nrOfSeekAds];
2583             xList[i]  = xList[nrOfSeekAds];
2584             yList[i]  = yList[nrOfSeekAds];
2585             zList[i]  = zList[nrOfSeekAds];
2586             seekAdList[nrOfSeekAds] = NULL;
2587             break;
2588         }
2589 }
2590
2591 Boolean
2592 MatchSoughtLine (char *line)
2593 {
2594     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2595     int nr, base, inc, u=0; char dummy;
2596
2597     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2598        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2599        (u=1) &&
2600        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2601         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2602         // match: compact and save the line
2603         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2604         return TRUE;
2605     }
2606     return FALSE;
2607 }
2608
2609 int
2610 DrawSeekGraph ()
2611 {
2612     int i;
2613     if(!seekGraphUp) return FALSE;
2614     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2615     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2616
2617     DrawSeekBackground(0, 0, w, h);
2618     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2619     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2620     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2621         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2622         yy = h-1-yy;
2623         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2624         if(i%500 == 0) {
2625             char buf[MSG_SIZ];
2626             snprintf(buf, MSG_SIZ, "%d", i);
2627             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2628         }
2629     }
2630     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2631     for(i=1; i<100; i+=(i<10?1:5)) {
2632         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2633         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2634         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2635             char buf[MSG_SIZ];
2636             snprintf(buf, MSG_SIZ, "%d", i);
2637             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2638         }
2639     }
2640     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2641     return TRUE;
2642 }
2643
2644 int
2645 SeekGraphClick (ClickType click, int x, int y, int moving)
2646 {
2647     static int lastDown = 0, displayed = 0, lastSecond;
2648     if(y < 0) return FALSE;
2649     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2650         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2651         if(!seekGraphUp) return FALSE;
2652         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2653         DrawPosition(TRUE, NULL);
2654         return TRUE;
2655     }
2656     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2657         if(click == Release || moving) return FALSE;
2658         nrOfSeekAds = 0;
2659         soughtPending = TRUE;
2660         SendToICS(ics_prefix);
2661         SendToICS("sought\n"); // should this be "sought all"?
2662     } else { // issue challenge based on clicked ad
2663         int dist = 10000; int i, closest = 0, second = 0;
2664         for(i=0; i<nrOfSeekAds; i++) {
2665             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2666             if(d < dist) { dist = d; closest = i; }
2667             second += (d - zList[i] < 120); // count in-range ads
2668             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2669         }
2670         if(dist < 120) {
2671             char buf[MSG_SIZ];
2672             second = (second > 1);
2673             if(displayed != closest || second != lastSecond) {
2674                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2675                 lastSecond = second; displayed = closest;
2676             }
2677             if(click == Press) {
2678                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2679                 lastDown = closest;
2680                 return TRUE;
2681             } // on press 'hit', only show info
2682             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2683             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2684             SendToICS(ics_prefix);
2685             SendToICS(buf);
2686             return TRUE; // let incoming board of started game pop down the graph
2687         } else if(click == Release) { // release 'miss' is ignored
2688             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2689             if(moving == 2) { // right up-click
2690                 nrOfSeekAds = 0; // refresh graph
2691                 soughtPending = TRUE;
2692                 SendToICS(ics_prefix);
2693                 SendToICS("sought\n"); // should this be "sought all"?
2694             }
2695             return TRUE;
2696         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2697         // press miss or release hit 'pop down' seek graph
2698         seekGraphUp = FALSE;
2699         DrawPosition(TRUE, NULL);
2700     }
2701     return TRUE;
2702 }
2703
2704 void
2705 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2706 {
2707 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2708 #define STARTED_NONE 0
2709 #define STARTED_MOVES 1
2710 #define STARTED_BOARD 2
2711 #define STARTED_OBSERVE 3
2712 #define STARTED_HOLDINGS 4
2713 #define STARTED_CHATTER 5
2714 #define STARTED_COMMENT 6
2715 #define STARTED_MOVES_NOHIDE 7
2716
2717     static int started = STARTED_NONE;
2718     static char parse[20000];
2719     static int parse_pos = 0;
2720     static char buf[BUF_SIZE + 1];
2721     static int firstTime = TRUE, intfSet = FALSE;
2722     static ColorClass prevColor = ColorNormal;
2723     static int savingComment = FALSE;
2724     static int cmatch = 0; // continuation sequence match
2725     char *bp;
2726     char str[MSG_SIZ];
2727     int i, oldi;
2728     int buf_len;
2729     int next_out;
2730     int tkind;
2731     int backup;    /* [DM] For zippy color lines */
2732     char *p;
2733     char talker[MSG_SIZ]; // [HGM] chat
2734     int channel;
2735
2736     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2737
2738     if (appData.debugMode) {
2739       if (!error) {
2740         fprintf(debugFP, "<ICS: ");
2741         show_bytes(debugFP, data, count);
2742         fprintf(debugFP, "\n");
2743       }
2744     }
2745
2746     if (appData.debugMode) { int f = forwardMostMove;
2747         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2748                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2749                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2750     }
2751     if (count > 0) {
2752         /* If last read ended with a partial line that we couldn't parse,
2753            prepend it to the new read and try again. */
2754         if (leftover_len > 0) {
2755             for (i=0; i<leftover_len; i++)
2756               buf[i] = buf[leftover_start + i];
2757         }
2758
2759     /* copy new characters into the buffer */
2760     bp = buf + leftover_len;
2761     buf_len=leftover_len;
2762     for (i=0; i<count; i++)
2763     {
2764         // ignore these
2765         if (data[i] == '\r')
2766             continue;
2767
2768         // join lines split by ICS?
2769         if (!appData.noJoin)
2770         {
2771             /*
2772                 Joining just consists of finding matches against the
2773                 continuation sequence, and discarding that sequence
2774                 if found instead of copying it.  So, until a match
2775                 fails, there's nothing to do since it might be the
2776                 complete sequence, and thus, something we don't want
2777                 copied.
2778             */
2779             if (data[i] == cont_seq[cmatch])
2780             {
2781                 cmatch++;
2782                 if (cmatch == strlen(cont_seq))
2783                 {
2784                     cmatch = 0; // complete match.  just reset the counter
2785
2786                     /*
2787                         it's possible for the ICS to not include the space
2788                         at the end of the last word, making our [correct]
2789                         join operation fuse two separate words.  the server
2790                         does this when the space occurs at the width setting.
2791                     */
2792                     if (!buf_len || buf[buf_len-1] != ' ')
2793                     {
2794                         *bp++ = ' ';
2795                         buf_len++;
2796                     }
2797                 }
2798                 continue;
2799             }
2800             else if (cmatch)
2801             {
2802                 /*
2803                     match failed, so we have to copy what matched before
2804                     falling through and copying this character.  In reality,
2805                     this will only ever be just the newline character, but
2806                     it doesn't hurt to be precise.
2807                 */
2808                 strncpy(bp, cont_seq, cmatch);
2809                 bp += cmatch;
2810                 buf_len += cmatch;
2811                 cmatch = 0;
2812             }
2813         }
2814
2815         // copy this char
2816         *bp++ = data[i];
2817         buf_len++;
2818     }
2819
2820         buf[buf_len] = NULLCHAR;
2821 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2822         next_out = 0;
2823         leftover_start = 0;
2824
2825         i = 0;
2826         while (i < buf_len) {
2827             /* Deal with part of the TELNET option negotiation
2828                protocol.  We refuse to do anything beyond the
2829                defaults, except that we allow the WILL ECHO option,
2830                which ICS uses to turn off password echoing when we are
2831                directly connected to it.  We reject this option
2832                if localLineEditing mode is on (always on in xboard)
2833                and we are talking to port 23, which might be a real
2834                telnet server that will try to keep WILL ECHO on permanently.
2835              */
2836             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2837                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2838                 unsigned char option;
2839                 oldi = i;
2840                 switch ((unsigned char) buf[++i]) {
2841                   case TN_WILL:
2842                     if (appData.debugMode)
2843                       fprintf(debugFP, "\n<WILL ");
2844                     switch (option = (unsigned char) buf[++i]) {
2845                       case TN_ECHO:
2846                         if (appData.debugMode)
2847                           fprintf(debugFP, "ECHO ");
2848                         /* Reply only if this is a change, according
2849                            to the protocol rules. */
2850                         if (remoteEchoOption) break;
2851                         if (appData.localLineEditing &&
2852                             atoi(appData.icsPort) == TN_PORT) {
2853                             TelnetRequest(TN_DONT, TN_ECHO);
2854                         } else {
2855                             EchoOff();
2856                             TelnetRequest(TN_DO, TN_ECHO);
2857                             remoteEchoOption = TRUE;
2858                         }
2859                         break;
2860                       default:
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", option);
2863                         /* Whatever this is, we don't want it. */
2864                         TelnetRequest(TN_DONT, option);
2865                         break;
2866                     }
2867                     break;
2868                   case TN_WONT:
2869                     if (appData.debugMode)
2870                       fprintf(debugFP, "\n<WONT ");
2871                     switch (option = (unsigned char) buf[++i]) {
2872                       case TN_ECHO:
2873                         if (appData.debugMode)
2874                           fprintf(debugFP, "ECHO ");
2875                         /* Reply only if this is a change, according
2876                            to the protocol rules. */
2877                         if (!remoteEchoOption) break;
2878                         EchoOn();
2879                         TelnetRequest(TN_DONT, TN_ECHO);
2880                         remoteEchoOption = FALSE;
2881                         break;
2882                       default:
2883                         if (appData.debugMode)
2884                           fprintf(debugFP, "%d ", (unsigned char) option);
2885                         /* Whatever this is, it must already be turned
2886                            off, because we never agree to turn on
2887                            anything non-default, so according to the
2888                            protocol rules, we don't reply. */
2889                         break;
2890                     }
2891                     break;
2892                   case TN_DO:
2893                     if (appData.debugMode)
2894                       fprintf(debugFP, "\n<DO ");
2895                     switch (option = (unsigned char) buf[++i]) {
2896                       default:
2897                         /* Whatever this is, we refuse to do it. */
2898                         if (appData.debugMode)
2899                           fprintf(debugFP, "%d ", option);
2900                         TelnetRequest(TN_WONT, option);
2901                         break;
2902                     }
2903                     break;
2904                   case TN_DONT:
2905                     if (appData.debugMode)
2906                       fprintf(debugFP, "\n<DONT ");
2907                     switch (option = (unsigned char) buf[++i]) {
2908                       default:
2909                         if (appData.debugMode)
2910                           fprintf(debugFP, "%d ", option);
2911                         /* Whatever this is, we are already not doing
2912                            it, because we never agree to do anything
2913                            non-default, so according to the protocol
2914                            rules, we don't reply. */
2915                         break;
2916                     }
2917                     break;
2918                   case TN_IAC:
2919                     if (appData.debugMode)
2920                       fprintf(debugFP, "\n<IAC ");
2921                     /* Doubled IAC; pass it through */
2922                     i--;
2923                     break;
2924                   default:
2925                     if (appData.debugMode)
2926                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2927                     /* Drop all other telnet commands on the floor */
2928                     break;
2929                 }
2930                 if (oldi > next_out)
2931                   SendToPlayer(&buf[next_out], oldi - next_out);
2932                 if (++i > next_out)
2933                   next_out = i;
2934                 continue;
2935             }
2936
2937             /* OK, this at least will *usually* work */
2938             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2939                 loggedOn = TRUE;
2940             }
2941
2942             if (loggedOn && !intfSet) {
2943                 if (ics_type == ICS_ICC) {
2944                   snprintf(str, MSG_SIZ,
2945                           "/set-quietly interface %s\n/set-quietly style 12\n",
2946                           programVersion);
2947                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2948                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2949                 } else if (ics_type == ICS_CHESSNET) {
2950                   snprintf(str, MSG_SIZ, "/style 12\n");
2951                 } else {
2952                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2953                   strcat(str, programVersion);
2954                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2955                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2956                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2957 #ifdef WIN32
2958                   strcat(str, "$iset nohighlight 1\n");
2959 #endif
2960                   strcat(str, "$iset lock 1\n$style 12\n");
2961                 }
2962                 SendToICS(str);
2963                 NotifyFrontendLogin();
2964                 intfSet = TRUE;
2965             }
2966
2967             if (started == STARTED_COMMENT) {
2968                 /* Accumulate characters in comment */
2969                 parse[parse_pos++] = buf[i];
2970                 if (buf[i] == '\n') {
2971                     parse[parse_pos] = NULLCHAR;
2972                     if(chattingPartner>=0) {
2973                         char mess[MSG_SIZ];
2974                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2975                         OutputChatMessage(chattingPartner, mess);
2976                         chattingPartner = -1;
2977                         next_out = i+1; // [HGM] suppress printing in ICS window
2978                     } else
2979                     if(!suppressKibitz) // [HGM] kibitz
2980                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2981                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2982                         int nrDigit = 0, nrAlph = 0, j;
2983                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2984                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2985                         parse[parse_pos] = NULLCHAR;
2986                         // try to be smart: if it does not look like search info, it should go to
2987                         // ICS interaction window after all, not to engine-output window.
2988                         for(j=0; j<parse_pos; j++) { // count letters and digits
2989                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2990                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2991                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2992                         }
2993                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2994                             int depth=0; float score;
2995                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2996                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2997                                 pvInfoList[forwardMostMove-1].depth = depth;
2998                                 pvInfoList[forwardMostMove-1].score = 100*score;
2999                             }
3000                             OutputKibitz(suppressKibitz, parse);
3001                         } else {
3002                             char tmp[MSG_SIZ];
3003                             if(gameMode == IcsObserving) // restore original ICS messages
3004                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3005                             else
3006                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3007                             SendToPlayer(tmp, strlen(tmp));
3008                         }
3009                         next_out = i+1; // [HGM] suppress printing in ICS window
3010                     }
3011                     started = STARTED_NONE;
3012                 } else {
3013                     /* Don't match patterns against characters in comment */
3014                     i++;
3015                     continue;
3016                 }
3017             }
3018             if (started == STARTED_CHATTER) {
3019                 if (buf[i] != '\n') {
3020                     /* Don't match patterns against characters in chatter */
3021                     i++;
3022                     continue;
3023                 }
3024                 started = STARTED_NONE;
3025                 if(suppressKibitz) next_out = i+1;
3026             }
3027
3028             /* Kludge to deal with rcmd protocol */
3029             if (firstTime && looking_at(buf, &i, "\001*")) {
3030                 DisplayFatalError(&buf[1], 0, 1);
3031                 continue;
3032             } else {
3033                 firstTime = FALSE;
3034             }
3035
3036             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3037                 ics_type = ICS_ICC;
3038                 ics_prefix = "/";
3039                 if (appData.debugMode)
3040                   fprintf(debugFP, "ics_type %d\n", ics_type);
3041                 continue;
3042             }
3043             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3044                 ics_type = ICS_FICS;
3045                 ics_prefix = "$";
3046                 if (appData.debugMode)
3047                   fprintf(debugFP, "ics_type %d\n", ics_type);
3048                 continue;
3049             }
3050             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3051                 ics_type = ICS_CHESSNET;
3052                 ics_prefix = "/";
3053                 if (appData.debugMode)
3054                   fprintf(debugFP, "ics_type %d\n", ics_type);
3055                 continue;
3056             }
3057
3058             if (!loggedOn &&
3059                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3060                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3061                  looking_at(buf, &i, "will be \"*\""))) {
3062               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3063               continue;
3064             }
3065
3066             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3067               char buf[MSG_SIZ];
3068               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3069               DisplayIcsInteractionTitle(buf);
3070               have_set_title = TRUE;
3071             }
3072
3073             /* skip finger notes */
3074             if (started == STARTED_NONE &&
3075                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3076                  (buf[i] == '1' && buf[i+1] == '0')) &&
3077                 buf[i+2] == ':' && buf[i+3] == ' ') {
3078               started = STARTED_CHATTER;
3079               i += 3;
3080               continue;
3081             }
3082
3083             oldi = i;
3084             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3085             if(appData.seekGraph) {
3086                 if(soughtPending && MatchSoughtLine(buf+i)) {
3087                     i = strstr(buf+i, "rated") - buf;
3088                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3089                     next_out = leftover_start = i;
3090                     started = STARTED_CHATTER;
3091                     suppressKibitz = TRUE;
3092                     continue;
3093                 }
3094                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3095                         && looking_at(buf, &i, "* ads displayed")) {
3096                     soughtPending = FALSE;
3097                     seekGraphUp = TRUE;
3098                     DrawSeekGraph();
3099                     continue;
3100                 }
3101                 if(appData.autoRefresh) {
3102                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3103                         int s = (ics_type == ICS_ICC); // ICC format differs
3104                         if(seekGraphUp)
3105                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3106                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3107                         looking_at(buf, &i, "*% "); // eat prompt
3108                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3109                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3110                         next_out = i; // suppress
3111                         continue;
3112                     }
3113                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3114                         char *p = star_match[0];
3115                         while(*p) {
3116                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3117                             while(*p && *p++ != ' '); // next
3118                         }
3119                         looking_at(buf, &i, "*% "); // eat prompt
3120                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3121                         next_out = i;
3122                         continue;
3123                     }
3124                 }
3125             }
3126
3127             /* skip formula vars */
3128             if (started == STARTED_NONE &&
3129                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3130               started = STARTED_CHATTER;
3131               i += 3;
3132               continue;
3133             }
3134
3135             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3136             if (appData.autoKibitz && started == STARTED_NONE &&
3137                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3138                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3139                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3140                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3141                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3142                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3143                         suppressKibitz = TRUE;
3144                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145                         next_out = i;
3146                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3147                                 && (gameMode == IcsPlayingWhite)) ||
3148                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3149                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3150                             started = STARTED_CHATTER; // own kibitz we simply discard
3151                         else {
3152                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3153                             parse_pos = 0; parse[0] = NULLCHAR;
3154                             savingComment = TRUE;
3155                             suppressKibitz = gameMode != IcsObserving ? 2 :
3156                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3157                         }
3158                         continue;
3159                 } else
3160                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3161                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3162                          && atoi(star_match[0])) {
3163                     // suppress the acknowledgements of our own autoKibitz
3164                     char *p;
3165                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3166                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3167                     SendToPlayer(star_match[0], strlen(star_match[0]));
3168                     if(looking_at(buf, &i, "*% ")) // eat prompt
3169                         suppressKibitz = FALSE;
3170                     next_out = i;
3171                     continue;
3172                 }
3173             } // [HGM] kibitz: end of patch
3174
3175             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3176
3177             // [HGM] chat: intercept tells by users for which we have an open chat window
3178             channel = -1;
3179             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3180                                            looking_at(buf, &i, "* whispers:") ||
3181                                            looking_at(buf, &i, "* kibitzes:") ||
3182                                            looking_at(buf, &i, "* shouts:") ||
3183                                            looking_at(buf, &i, "* c-shouts:") ||
3184                                            looking_at(buf, &i, "--> * ") ||
3185                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3186                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3187                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3188                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3189                 int p;
3190                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3191                 chattingPartner = -1;
3192
3193                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3194                 for(p=0; p<MAX_CHAT; p++) {
3195                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3196                     talker[0] = '['; strcat(talker, "] ");
3197                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3198                     chattingPartner = p; break;
3199                     }
3200                 } else
3201                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3202                 for(p=0; p<MAX_CHAT; p++) {
3203                     if(!strcmp("kibitzes", chatPartner[p])) {
3204                         talker[0] = '['; strcat(talker, "] ");
3205                         chattingPartner = p; break;
3206                     }
3207                 } else
3208                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3209                 for(p=0; p<MAX_CHAT; p++) {
3210                     if(!strcmp("whispers", chatPartner[p])) {
3211                         talker[0] = '['; strcat(talker, "] ");
3212                         chattingPartner = p; break;
3213                     }
3214                 } else
3215                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3216                   if(buf[i-8] == '-' && buf[i-3] == 't')
3217                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3218                     if(!strcmp("c-shouts", chatPartner[p])) {
3219                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3220                         chattingPartner = p; break;
3221                     }
3222                   }
3223                   if(chattingPartner < 0)
3224                   for(p=0; p<MAX_CHAT; p++) {
3225                     if(!strcmp("shouts", chatPartner[p])) {
3226                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3227                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3228                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3229                         chattingPartner = p; break;
3230                     }
3231                   }
3232                 }
3233                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3234                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3235                     talker[0] = 0; Colorize(ColorTell, FALSE);
3236                     chattingPartner = p; break;
3237                 }
3238                 if(chattingPartner<0) i = oldi; else {
3239                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3240                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3241                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242                     started = STARTED_COMMENT;
3243                     parse_pos = 0; parse[0] = NULLCHAR;
3244                     savingComment = 3 + chattingPartner; // counts as TRUE
3245                     suppressKibitz = TRUE;
3246                     continue;
3247                 }
3248             } // [HGM] chat: end of patch
3249
3250           backup = i;
3251             if (appData.zippyTalk || appData.zippyPlay) {
3252                 /* [DM] Backup address for color zippy lines */
3253 #if ZIPPY
3254                if (loggedOn == TRUE)
3255                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3256                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3257 #endif
3258             } // [DM] 'else { ' deleted
3259                 if (
3260                     /* Regular tells and says */
3261                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3262                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3263                     looking_at(buf, &i, "* says: ") ||
3264                     /* Don't color "message" or "messages" output */
3265                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3266                     looking_at(buf, &i, "*. * at *:*: ") ||
3267                     looking_at(buf, &i, "--* (*:*): ") ||
3268                     /* Message notifications (same color as tells) */
3269                     looking_at(buf, &i, "* has left a message ") ||
3270                     looking_at(buf, &i, "* just sent you a message:\n") ||
3271                     /* Whispers and kibitzes */
3272                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3273                     looking_at(buf, &i, "* kibitzes: ") ||
3274                     /* Channel tells */
3275                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3276
3277                   if (tkind == 1 && strchr(star_match[0], ':')) {
3278                       /* Avoid "tells you:" spoofs in channels */
3279                      tkind = 3;
3280                   }
3281                   if (star_match[0][0] == NULLCHAR ||
3282                       strchr(star_match[0], ' ') ||
3283                       (tkind == 3 && strchr(star_match[1], ' '))) {
3284                     /* Reject bogus matches */
3285                     i = oldi;
3286                   } else {
3287                     if (appData.colorize) {
3288                       if (oldi > next_out) {
3289                         SendToPlayer(&buf[next_out], oldi - next_out);
3290                         next_out = oldi;
3291                       }
3292                       switch (tkind) {
3293                       case 1:
3294                         Colorize(ColorTell, FALSE);
3295                         curColor = ColorTell;
3296                         break;
3297                       case 2:
3298                         Colorize(ColorKibitz, FALSE);
3299                         curColor = ColorKibitz;
3300                         break;
3301                       case 3:
3302                         p = strrchr(star_match[1], '(');
3303                         if (p == NULL) {
3304                           p = star_match[1];
3305                         } else {
3306                           p++;
3307                         }
3308                         if (atoi(p) == 1) {
3309                           Colorize(ColorChannel1, FALSE);
3310                           curColor = ColorChannel1;
3311                         } else {
3312                           Colorize(ColorChannel, FALSE);
3313                           curColor = ColorChannel;
3314                         }
3315                         break;
3316                       case 5:
3317                         curColor = ColorNormal;
3318                         break;
3319                       }
3320                     }
3321                     if (started == STARTED_NONE && appData.autoComment &&
3322                         (gameMode == IcsObserving ||
3323                          gameMode == IcsPlayingWhite ||
3324                          gameMode == IcsPlayingBlack)) {
3325                       parse_pos = i - oldi;
3326                       memcpy(parse, &buf[oldi], parse_pos);
3327                       parse[parse_pos] = NULLCHAR;
3328                       started = STARTED_COMMENT;
3329                       savingComment = TRUE;
3330                     } else {
3331                       started = STARTED_CHATTER;
3332                       savingComment = FALSE;
3333                     }
3334                     loggedOn = TRUE;
3335                     continue;
3336                   }
3337                 }
3338
3339                 if (looking_at(buf, &i, "* s-shouts: ") ||
3340                     looking_at(buf, &i, "* c-shouts: ")) {
3341                     if (appData.colorize) {
3342                         if (oldi > next_out) {
3343                             SendToPlayer(&buf[next_out], oldi - next_out);
3344                             next_out = oldi;
3345                         }
3346                         Colorize(ColorSShout, FALSE);
3347                         curColor = ColorSShout;
3348                     }
3349                     loggedOn = TRUE;
3350                     started = STARTED_CHATTER;
3351                     continue;
3352                 }
3353
3354                 if (looking_at(buf, &i, "--->")) {
3355                     loggedOn = TRUE;
3356                     continue;
3357                 }
3358
3359                 if (looking_at(buf, &i, "* shouts: ") ||
3360                     looking_at(buf, &i, "--> ")) {
3361                     if (appData.colorize) {
3362                         if (oldi > next_out) {
3363                             SendToPlayer(&buf[next_out], oldi - next_out);
3364                             next_out = oldi;
3365                         }
3366                         Colorize(ColorShout, FALSE);
3367                         curColor = ColorShout;
3368                     }
3369                     loggedOn = TRUE;
3370                     started = STARTED_CHATTER;
3371                     continue;
3372                 }
3373
3374                 if (looking_at( buf, &i, "Challenge:")) {
3375                     if (appData.colorize) {
3376                         if (oldi > next_out) {
3377                             SendToPlayer(&buf[next_out], oldi - next_out);
3378                             next_out = oldi;
3379                         }
3380                         Colorize(ColorChallenge, FALSE);
3381                         curColor = ColorChallenge;
3382                     }
3383                     loggedOn = TRUE;
3384                     continue;
3385                 }
3386
3387                 if (looking_at(buf, &i, "* offers you") ||
3388                     looking_at(buf, &i, "* offers to be") ||
3389                     looking_at(buf, &i, "* would like to") ||
3390                     looking_at(buf, &i, "* requests to") ||
3391                     looking_at(buf, &i, "Your opponent offers") ||
3392                     looking_at(buf, &i, "Your opponent requests")) {
3393
3394                     if (appData.colorize) {
3395                         if (oldi > next_out) {
3396                             SendToPlayer(&buf[next_out], oldi - next_out);
3397                             next_out = oldi;
3398                         }
3399                         Colorize(ColorRequest, FALSE);
3400                         curColor = ColorRequest;
3401                     }
3402                     continue;
3403                 }
3404
3405                 if (looking_at(buf, &i, "* (*) seeking")) {
3406                     if (appData.colorize) {
3407                         if (oldi > next_out) {
3408                             SendToPlayer(&buf[next_out], oldi - next_out);
3409                             next_out = oldi;
3410                         }
3411                         Colorize(ColorSeek, FALSE);
3412                         curColor = ColorSeek;
3413                     }
3414                     continue;
3415             }
3416
3417           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3418
3419             if (looking_at(buf, &i, "\\   ")) {
3420                 if (prevColor != ColorNormal) {
3421                     if (oldi > next_out) {
3422                         SendToPlayer(&buf[next_out], oldi - next_out);
3423                         next_out = oldi;
3424                     }
3425                     Colorize(prevColor, TRUE);
3426                     curColor = prevColor;
3427                 }
3428                 if (savingComment) {
3429                     parse_pos = i - oldi;
3430                     memcpy(parse, &buf[oldi], parse_pos);
3431                     parse[parse_pos] = NULLCHAR;
3432                     started = STARTED_COMMENT;
3433                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3434                         chattingPartner = savingComment - 3; // kludge to remember the box
3435                 } else {
3436                     started = STARTED_CHATTER;
3437                 }
3438                 continue;
3439             }
3440
3441             if (looking_at(buf, &i, "Black Strength :") ||
3442                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3443                 looking_at(buf, &i, "<10>") ||
3444                 looking_at(buf, &i, "#@#")) {
3445                 /* Wrong board style */
3446                 loggedOn = TRUE;
3447                 SendToICS(ics_prefix);
3448                 SendToICS("set style 12\n");
3449                 SendToICS(ics_prefix);
3450                 SendToICS("refresh\n");
3451                 continue;
3452             }
3453
3454             if (looking_at(buf, &i, "login:")) {
3455               if (!have_sent_ICS_logon) {
3456                 if(ICSInitScript())
3457                   have_sent_ICS_logon = 1;
3458                 else // no init script was found
3459                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3460               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3461                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3462               }
3463                 continue;
3464             }
3465
3466             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3467                 (looking_at(buf, &i, "\n<12> ") ||
3468                  looking_at(buf, &i, "<12> "))) {
3469                 loggedOn = TRUE;
3470                 if (oldi > next_out) {
3471                     SendToPlayer(&buf[next_out], oldi - next_out);
3472                 }
3473                 next_out = i;
3474                 started = STARTED_BOARD;
3475                 parse_pos = 0;
3476                 continue;
3477             }
3478
3479             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3480                 looking_at(buf, &i, "<b1> ")) {
3481                 if (oldi > next_out) {
3482                     SendToPlayer(&buf[next_out], oldi - next_out);
3483                 }
3484                 next_out = i;
3485                 started = STARTED_HOLDINGS;
3486                 parse_pos = 0;
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3491                 loggedOn = TRUE;
3492                 /* Header for a move list -- first line */
3493
3494                 switch (ics_getting_history) {
3495                   case H_FALSE:
3496                     switch (gameMode) {
3497                       case IcsIdle:
3498                       case BeginningOfGame:
3499                         /* User typed "moves" or "oldmoves" while we
3500                            were idle.  Pretend we asked for these
3501                            moves and soak them up so user can step
3502                            through them and/or save them.
3503                            */
3504                         Reset(FALSE, TRUE);
3505                         gameMode = IcsObserving;
3506                         ModeHighlight();
3507                         ics_gamenum = -1;
3508                         ics_getting_history = H_GOT_UNREQ_HEADER;
3509                         break;
3510                       case EditGame: /*?*/
3511                       case EditPosition: /*?*/
3512                         /* Should above feature work in these modes too? */
3513                         /* For now it doesn't */
3514                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3515                         break;
3516                       default:
3517                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3518                         break;
3519                     }
3520                     break;
3521                   case H_REQUESTED:
3522                     /* Is this the right one? */
3523                     if (gameInfo.white && gameInfo.black &&
3524                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3525                         strcmp(gameInfo.black, star_match[2]) == 0) {
3526                         /* All is well */
3527                         ics_getting_history = H_GOT_REQ_HEADER;
3528                     }
3529                     break;
3530                   case H_GOT_REQ_HEADER:
3531                   case H_GOT_UNREQ_HEADER:
3532                   case H_GOT_UNWANTED_HEADER:
3533                   case H_GETTING_MOVES:
3534                     /* Should not happen */
3535                     DisplayError(_("Error gathering move list: two headers"), 0);
3536                     ics_getting_history = H_FALSE;
3537                     break;
3538                 }
3539
3540                 /* Save player ratings into gameInfo if needed */
3541                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3542                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3543                     (gameInfo.whiteRating == -1 ||
3544                      gameInfo.blackRating == -1)) {
3545
3546                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3547                     gameInfo.blackRating = string_to_rating(star_match[3]);
3548                     if (appData.debugMode)
3549                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3550                               gameInfo.whiteRating, gameInfo.blackRating);
3551                 }
3552                 continue;
3553             }
3554
3555             if (looking_at(buf, &i,
3556               "* * match, initial time: * minute*, increment: * second")) {
3557                 /* Header for a move list -- second line */
3558                 /* Initial board will follow if this is a wild game */
3559                 if (gameInfo.event != NULL) free(gameInfo.event);
3560                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3561                 gameInfo.event = StrSave(str);
3562                 /* [HGM] we switched variant. Translate boards if needed. */
3563                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3564                 continue;
3565             }
3566
3567             if (looking_at(buf, &i, "Move  ")) {
3568                 /* Beginning of a move list */
3569                 switch (ics_getting_history) {
3570                   case H_FALSE:
3571                     /* Normally should not happen */
3572                     /* Maybe user hit reset while we were parsing */
3573                     break;
3574                   case H_REQUESTED:
3575                     /* Happens if we are ignoring a move list that is not
3576                      * the one we just requested.  Common if the user
3577                      * tries to observe two games without turning off
3578                      * getMoveList */
3579                     break;
3580                   case H_GETTING_MOVES:
3581                     /* Should not happen */
3582                     DisplayError(_("Error gathering move list: nested"), 0);
3583                     ics_getting_history = H_FALSE;
3584                     break;
3585                   case H_GOT_REQ_HEADER:
3586                     ics_getting_history = H_GETTING_MOVES;
3587                     started = STARTED_MOVES;
3588                     parse_pos = 0;
3589                     if (oldi > next_out) {
3590                         SendToPlayer(&buf[next_out], oldi - next_out);
3591                     }
3592                     break;
3593                   case H_GOT_UNREQ_HEADER:
3594                     ics_getting_history = H_GETTING_MOVES;
3595                     started = STARTED_MOVES_NOHIDE;
3596                     parse_pos = 0;
3597                     break;
3598                   case H_GOT_UNWANTED_HEADER:
3599                     ics_getting_history = H_FALSE;
3600                     break;
3601                 }
3602                 continue;
3603             }
3604
3605             if (looking_at(buf, &i, "% ") ||
3606                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3607                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3608                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3609                     soughtPending = FALSE;
3610                     seekGraphUp = TRUE;
3611                     DrawSeekGraph();
3612                 }
3613                 if(suppressKibitz) next_out = i;
3614                 savingComment = FALSE;
3615                 suppressKibitz = 0;
3616                 switch (started) {
3617                   case STARTED_MOVES:
3618                   case STARTED_MOVES_NOHIDE:
3619                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3620                     parse[parse_pos + i - oldi] = NULLCHAR;
3621                     ParseGameHistory(parse);
3622 #if ZIPPY
3623                     if (appData.zippyPlay && first.initDone) {
3624                         FeedMovesToProgram(&first, forwardMostMove);
3625                         if (gameMode == IcsPlayingWhite) {
3626                             if (WhiteOnMove(forwardMostMove)) {
3627                                 if (first.sendTime) {
3628                                   if (first.useColors) {
3629                                     SendToProgram("black\n", &first);
3630                                   }
3631                                   SendTimeRemaining(&first, TRUE);
3632                                 }
3633                                 if (first.useColors) {
3634                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3635                                 }
3636                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3637                                 first.maybeThinking = TRUE;
3638                             } else {
3639                                 if (first.usePlayother) {
3640                                   if (first.sendTime) {
3641                                     SendTimeRemaining(&first, TRUE);
3642                                   }
3643                                   SendToProgram("playother\n", &first);
3644                                   firstMove = FALSE;
3645                                 } else {
3646                                   firstMove = TRUE;
3647                                 }
3648                             }
3649                         } else if (gameMode == IcsPlayingBlack) {
3650                             if (!WhiteOnMove(forwardMostMove)) {
3651                                 if (first.sendTime) {
3652                                   if (first.useColors) {
3653                                     SendToProgram("white\n", &first);
3654                                   }
3655                                   SendTimeRemaining(&first, FALSE);
3656                                 }
3657                                 if (first.useColors) {
3658                                   SendToProgram("black\n", &first);
3659                                 }
3660                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3661                                 first.maybeThinking = TRUE;
3662                             } else {
3663                                 if (first.usePlayother) {
3664                                   if (first.sendTime) {
3665                                     SendTimeRemaining(&first, FALSE);
3666                                   }
3667                                   SendToProgram("playother\n", &first);
3668                                   firstMove = FALSE;
3669                                 } else {
3670                                   firstMove = TRUE;
3671                                 }
3672                             }
3673                         }
3674                     }
3675 #endif
3676                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3677                         /* Moves came from oldmoves or moves command
3678                            while we weren't doing anything else.
3679                            */
3680                         currentMove = forwardMostMove;
3681                         ClearHighlights();/*!!could figure this out*/
3682                         flipView = appData.flipView;
3683                         DrawPosition(TRUE, boards[currentMove]);
3684                         DisplayBothClocks();
3685                         snprintf(str, MSG_SIZ, "%s %s %s",
3686                                 gameInfo.white, _("vs."),  gameInfo.black);
3687                         DisplayTitle(str);
3688                         gameMode = IcsIdle;
3689                     } else {
3690                         /* Moves were history of an active game */
3691                         if (gameInfo.resultDetails != NULL) {
3692                             free(gameInfo.resultDetails);
3693                             gameInfo.resultDetails = NULL;
3694                         }
3695                     }
3696                     HistorySet(parseList, backwardMostMove,
3697                                forwardMostMove, currentMove-1);
3698                     DisplayMove(currentMove - 1);
3699                     if (started == STARTED_MOVES) next_out = i;
3700                     started = STARTED_NONE;
3701                     ics_getting_history = H_FALSE;
3702                     break;
3703
3704                   case STARTED_OBSERVE:
3705                     started = STARTED_NONE;
3706                     SendToICS(ics_prefix);
3707                     SendToICS("refresh\n");
3708                     break;
3709
3710                   default:
3711                     break;
3712                 }
3713                 if(bookHit) { // [HGM] book: simulate book reply
3714                     static char bookMove[MSG_SIZ]; // a bit generous?
3715
3716                     programStats.nodes = programStats.depth = programStats.time =
3717                     programStats.score = programStats.got_only_move = 0;
3718                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3719
3720                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3721                     strcat(bookMove, bookHit);
3722                     HandleMachineMove(bookMove, &first);
3723                 }
3724                 continue;
3725             }
3726
3727             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3728                  started == STARTED_HOLDINGS ||
3729                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3730                 /* Accumulate characters in move list or board */
3731                 parse[parse_pos++] = buf[i];
3732             }
3733
3734             /* Start of game messages.  Mostly we detect start of game
3735                when the first board image arrives.  On some versions
3736                of the ICS, though, we need to do a "refresh" after starting
3737                to observe in order to get the current board right away. */
3738             if (looking_at(buf, &i, "Adding game * to observation list")) {
3739                 started = STARTED_OBSERVE;
3740                 continue;
3741             }
3742
3743             /* Handle auto-observe */
3744             if (appData.autoObserve &&
3745                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3746                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3747                 char *player;
3748                 /* Choose the player that was highlighted, if any. */
3749                 if (star_match[0][0] == '\033' ||
3750                     star_match[1][0] != '\033') {
3751                     player = star_match[0];
3752                 } else {
3753                     player = star_match[2];
3754                 }
3755                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3756                         ics_prefix, StripHighlightAndTitle(player));
3757                 SendToICS(str);
3758
3759                 /* Save ratings from notify string */
3760                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3761                 player1Rating = string_to_rating(star_match[1]);
3762                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3763                 player2Rating = string_to_rating(star_match[3]);
3764
3765                 if (appData.debugMode)
3766                   fprintf(debugFP,
3767                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3768                           player1Name, player1Rating,
3769                           player2Name, player2Rating);
3770
3771                 continue;
3772             }
3773
3774             /* Deal with automatic examine mode after a game,
3775                and with IcsObserving -> IcsExamining transition */
3776             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3777                 looking_at(buf, &i, "has made you an examiner of game *")) {
3778
3779                 int gamenum = atoi(star_match[0]);
3780                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3781                     gamenum == ics_gamenum) {
3782                     /* We were already playing or observing this game;
3783                        no need to refetch history */
3784                     gameMode = IcsExamining;
3785                     if (pausing) {
3786                         pauseExamForwardMostMove = forwardMostMove;
3787                     } else if (currentMove < forwardMostMove) {
3788                         ForwardInner(forwardMostMove);
3789                     }
3790                 } else {
3791                     /* I don't think this case really can happen */
3792                     SendToICS(ics_prefix);
3793                     SendToICS("refresh\n");
3794                 }
3795                 continue;
3796             }
3797
3798             /* Error messages */
3799 //          if (ics_user_moved) {
3800             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3801                 if (looking_at(buf, &i, "Illegal move") ||
3802                     looking_at(buf, &i, "Not a legal move") ||
3803                     looking_at(buf, &i, "Your king is in check") ||
3804                     looking_at(buf, &i, "It isn't your turn") ||
3805                     looking_at(buf, &i, "It is not your move")) {
3806                     /* Illegal move */
3807                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3808                         currentMove = forwardMostMove-1;
3809                         DisplayMove(currentMove - 1); /* before DMError */
3810                         DrawPosition(FALSE, boards[currentMove]);
3811                         SwitchClocks(forwardMostMove-1); // [HGM] race
3812                         DisplayBothClocks();
3813                     }
3814                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3815                     ics_user_moved = 0;
3816                     continue;
3817                 }
3818             }
3819
3820             if (looking_at(buf, &i, "still have time") ||
3821                 looking_at(buf, &i, "not out of time") ||
3822                 looking_at(buf, &i, "either player is out of time") ||
3823                 looking_at(buf, &i, "has timeseal; checking")) {
3824                 /* We must have called his flag a little too soon */
3825                 whiteFlag = blackFlag = FALSE;
3826                 continue;
3827             }
3828
3829             if (looking_at(buf, &i, "added * seconds to") ||
3830                 looking_at(buf, &i, "seconds were added to")) {
3831                 /* Update the clocks */
3832                 SendToICS(ics_prefix);
3833                 SendToICS("refresh\n");
3834                 continue;
3835             }
3836
3837             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3838                 ics_clock_paused = TRUE;
3839                 StopClocks();
3840                 continue;
3841             }
3842
3843             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3844                 ics_clock_paused = FALSE;
3845                 StartClocks();
3846                 continue;
3847             }
3848
3849             /* Grab player ratings from the Creating: message.
3850                Note we have to check for the special case when
3851                the ICS inserts things like [white] or [black]. */
3852             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3853                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3854                 /* star_matches:
3855                    0    player 1 name (not necessarily white)
3856                    1    player 1 rating
3857                    2    empty, white, or black (IGNORED)
3858                    3    player 2 name (not necessarily black)
3859                    4    player 2 rating
3860
3861                    The names/ratings are sorted out when the game
3862                    actually starts (below).
3863                 */
3864                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3865                 player1Rating = string_to_rating(star_match[1]);
3866                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3867                 player2Rating = string_to_rating(star_match[4]);
3868
3869                 if (appData.debugMode)
3870                   fprintf(debugFP,
3871                           "Ratings from 'Creating:' %s %d, %s %d\n",
3872                           player1Name, player1Rating,
3873                           player2Name, player2Rating);
3874
3875                 continue;
3876             }
3877
3878             /* Improved generic start/end-of-game messages */
3879             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3880                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3881                 /* If tkind == 0: */
3882                 /* star_match[0] is the game number */
3883                 /*           [1] is the white player's name */
3884                 /*           [2] is the black player's name */
3885                 /* For end-of-game: */
3886                 /*           [3] is the reason for the game end */
3887                 /*           [4] is a PGN end game-token, preceded by " " */
3888                 /* For start-of-game: */
3889                 /*           [3] begins with "Creating" or "Continuing" */
3890                 /*           [4] is " *" or empty (don't care). */
3891                 int gamenum = atoi(star_match[0]);
3892                 char *whitename, *blackname, *why, *endtoken;
3893                 ChessMove endtype = EndOfFile;
3894
3895                 if (tkind == 0) {
3896                   whitename = star_match[1];
3897                   blackname = star_match[2];
3898                   why = star_match[3];
3899                   endtoken = star_match[4];
3900                 } else {
3901                   whitename = star_match[1];
3902                   blackname = star_match[3];
3903                   why = star_match[5];
3904                   endtoken = star_match[6];
3905                 }
3906
3907                 /* Game start messages */
3908                 if (strncmp(why, "Creating ", 9) == 0 ||
3909                     strncmp(why, "Continuing ", 11) == 0) {
3910                     gs_gamenum = gamenum;
3911                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3912                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3913                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3914 #if ZIPPY
3915                     if (appData.zippyPlay) {
3916                         ZippyGameStart(whitename, blackname);
3917                     }
3918 #endif /*ZIPPY*/
3919                     partnerBoardValid = FALSE; // [HGM] bughouse
3920                     continue;
3921                 }
3922
3923                 /* Game end messages */
3924                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3925                     ics_gamenum != gamenum) {
3926                     continue;
3927                 }
3928                 while (endtoken[0] == ' ') endtoken++;
3929                 switch (endtoken[0]) {
3930                   case '*':
3931                   default:
3932                     endtype = GameUnfinished;
3933                     break;
3934                   case '0':
3935                     endtype = BlackWins;
3936                     break;
3937                   case '1':
3938                     if (endtoken[1] == '/')
3939                       endtype = GameIsDrawn;
3940                     else
3941                       endtype = WhiteWins;
3942                     break;
3943                 }
3944                 GameEnds(endtype, why, GE_ICS);
3945 #if ZIPPY
3946                 if (appData.zippyPlay && first.initDone) {
3947                     ZippyGameEnd(endtype, why);
3948                     if (first.pr == NoProc) {
3949                       /* Start the next process early so that we'll
3950                          be ready for the next challenge */
3951                       StartChessProgram(&first);
3952                     }
3953                     /* Send "new" early, in case this command takes
3954                        a long time to finish, so that we'll be ready
3955                        for the next challenge. */
3956                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3957                     Reset(TRUE, TRUE);
3958                 }
3959 #endif /*ZIPPY*/
3960                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "Removing game * from observation") ||
3965                 looking_at(buf, &i, "no longer observing game *") ||
3966                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3967                 if (gameMode == IcsObserving &&
3968                     atoi(star_match[0]) == ics_gamenum)
3969                   {
3970                       /* icsEngineAnalyze */
3971                       if (appData.icsEngineAnalyze) {
3972                             ExitAnalyzeMode();
3973                             ModeHighlight();
3974                       }
3975                       StopClocks();
3976                       gameMode = IcsIdle;
3977                       ics_gamenum = -1;
3978                       ics_user_moved = FALSE;
3979                   }
3980                 continue;
3981             }
3982
3983             if (looking_at(buf, &i, "no longer examining game *")) {
3984                 if (gameMode == IcsExamining &&
3985                     atoi(star_match[0]) == ics_gamenum)
3986                   {
3987                       gameMode = IcsIdle;
3988                       ics_gamenum = -1;
3989                       ics_user_moved = FALSE;
3990                   }
3991                 continue;
3992             }
3993
3994             /* Advance leftover_start past any newlines we find,
3995                so only partial lines can get reparsed */
3996             if (looking_at(buf, &i, "\n")) {
3997                 prevColor = curColor;
3998                 if (curColor != ColorNormal) {
3999                     if (oldi > next_out) {
4000                         SendToPlayer(&buf[next_out], oldi - next_out);
4001                         next_out = oldi;
4002                     }
4003                     Colorize(ColorNormal, FALSE);
4004                     curColor = ColorNormal;
4005                 }
4006                 if (started == STARTED_BOARD) {
4007                     started = STARTED_NONE;
4008                     parse[parse_pos] = NULLCHAR;
4009                     ParseBoard12(parse);
4010                     ics_user_moved = 0;
4011
4012                     /* Send premove here */
4013                     if (appData.premove) {
4014                       char str[MSG_SIZ];
4015                       if (currentMove == 0 &&
4016                           gameMode == IcsPlayingWhite &&
4017                           appData.premoveWhite) {
4018                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4019                         if (appData.debugMode)
4020                           fprintf(debugFP, "Sending premove:\n");
4021                         SendToICS(str);
4022                       } else if (currentMove == 1 &&
4023                                  gameMode == IcsPlayingBlack &&
4024                                  appData.premoveBlack) {
4025                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4026                         if (appData.debugMode)
4027                           fprintf(debugFP, "Sending premove:\n");
4028                         SendToICS(str);
4029                       } else if (gotPremove) {
4030                         gotPremove = 0;
4031                         ClearPremoveHighlights();
4032                         if (appData.debugMode)
4033                           fprintf(debugFP, "Sending premove:\n");
4034                           UserMoveEvent(premoveFromX, premoveFromY,
4035                                         premoveToX, premoveToY,
4036                                         premovePromoChar);
4037                       }
4038                     }
4039
4040                     /* Usually suppress following prompt */
4041                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4042                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4043                         if (looking_at(buf, &i, "*% ")) {
4044                             savingComment = FALSE;
4045                             suppressKibitz = 0;
4046                         }
4047                     }
4048                     next_out = i;
4049                 } else if (started == STARTED_HOLDINGS) {
4050                     int gamenum;
4051                     char new_piece[MSG_SIZ];
4052                     started = STARTED_NONE;
4053                     parse[parse_pos] = NULLCHAR;
4054                     if (appData.debugMode)
4055                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4056                                                         parse, currentMove);
4057                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4058                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4059                         if (gameInfo.variant == VariantNormal) {
4060                           /* [HGM] We seem to switch variant during a game!
4061                            * Presumably no holdings were displayed, so we have
4062                            * to move the position two files to the right to
4063                            * create room for them!
4064                            */
4065                           VariantClass newVariant;
4066                           switch(gameInfo.boardWidth) { // base guess on board width
4067                                 case 9:  newVariant = VariantShogi; break;
4068                                 case 10: newVariant = VariantGreat; break;
4069                                 default: newVariant = VariantCrazyhouse; break;
4070                           }
4071                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4072                           /* Get a move list just to see the header, which
4073                              will tell us whether this is really bug or zh */
4074                           if (ics_getting_history == H_FALSE) {
4075                             ics_getting_history = H_REQUESTED;
4076                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4077                             SendToICS(str);
4078                           }
4079                         }
4080                         new_piece[0] = NULLCHAR;
4081                         sscanf(parse, "game %d white [%s black [%s <- %s",
4082                                &gamenum, white_holding, black_holding,
4083                                new_piece);
4084                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4085                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4086                         /* [HGM] copy holdings to board holdings area */
4087                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4088                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4089                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4090 #if ZIPPY
4091                         if (appData.zippyPlay && first.initDone) {
4092                             ZippyHoldings(white_holding, black_holding,
4093                                           new_piece);
4094                         }
4095 #endif /*ZIPPY*/
4096                         if (tinyLayout || smallLayout) {
4097                             char wh[16], bh[16];
4098                             PackHolding(wh, white_holding);
4099                             PackHolding(bh, black_holding);
4100                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4101                                     gameInfo.white, gameInfo.black);
4102                         } else {
4103                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4104                                     gameInfo.white, white_holding, _("vs."),
4105                                     gameInfo.black, black_holding);
4106                         }
4107                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4108                         DrawPosition(FALSE, boards[currentMove]);
4109                         DisplayTitle(str);
4110                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4111                         sscanf(parse, "game %d white [%s black [%s <- %s",
4112                                &gamenum, white_holding, black_holding,
4113                                new_piece);
4114                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4115                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4116                         /* [HGM] copy holdings to partner-board holdings area */
4117                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4118                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4119                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4120                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4121                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4122                       }
4123                     }
4124                     /* Suppress following prompt */
4125                     if (looking_at(buf, &i, "*% ")) {
4126                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4127                         savingComment = FALSE;
4128                         suppressKibitz = 0;
4129                     }
4130                     next_out = i;
4131                 }
4132                 continue;
4133             }
4134
4135             i++;                /* skip unparsed character and loop back */
4136         }
4137
4138         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4139 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4140 //          SendToPlayer(&buf[next_out], i - next_out);
4141             started != STARTED_HOLDINGS && leftover_start > next_out) {
4142             SendToPlayer(&buf[next_out], leftover_start - next_out);
4143             next_out = i;
4144         }
4145
4146         leftover_len = buf_len - leftover_start;
4147         /* if buffer ends with something we couldn't parse,
4148            reparse it after appending the next read */
4149
4150     } else if (count == 0) {
4151         RemoveInputSource(isr);
4152         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4153     } else {
4154         DisplayFatalError(_("Error reading from ICS"), error, 1);
4155     }
4156 }
4157
4158
4159 /* Board style 12 looks like this:
4160
4161    <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
4162
4163  * The "<12> " is stripped before it gets to this routine.  The two
4164  * trailing 0's (flip state and clock ticking) are later addition, and
4165  * some chess servers may not have them, or may have only the first.
4166  * Additional trailing fields may be added in the future.
4167  */
4168
4169 #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"
4170
4171 #define RELATION_OBSERVING_PLAYED    0
4172 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4173 #define RELATION_PLAYING_MYMOVE      1
4174 #define RELATION_PLAYING_NOTMYMOVE  -1
4175 #define RELATION_EXAMINING           2
4176 #define RELATION_ISOLATED_BOARD     -3
4177 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4178
4179 void
4180 ParseBoard12 (char *string)
4181 {
4182     GameMode newGameMode;
4183     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4184     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4185     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4186     char to_play, board_chars[200];
4187     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4188     char black[32], white[32];
4189     Board board;
4190     int prevMove = currentMove;
4191     int ticking = 2;
4192     ChessMove moveType;
4193     int fromX, fromY, toX, toY;
4194     char promoChar;
4195     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4196     char *bookHit = NULL; // [HGM] book
4197     Boolean weird = FALSE, reqFlag = FALSE;
4198
4199     fromX = fromY = toX = toY = -1;
4200
4201     newGame = FALSE;
4202
4203     if (appData.debugMode)
4204       fprintf(debugFP, _("Parsing board: %s\n"), string);
4205
4206     move_str[0] = NULLCHAR;
4207     elapsed_time[0] = NULLCHAR;
4208     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4209         int  i = 0, j;
4210         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4211             if(string[i] == ' ') { ranks++; files = 0; }
4212             else files++;
4213             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4214             i++;
4215         }
4216         for(j = 0; j <i; j++) board_chars[j] = string[j];
4217         board_chars[i] = '\0';
4218         string += i + 1;
4219     }
4220     n = sscanf(string, PATTERN, &to_play, &double_push,
4221                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4222                &gamenum, white, black, &relation, &basetime, &increment,
4223                &white_stren, &black_stren, &white_time, &black_time,
4224                &moveNum, str, elapsed_time, move_str, &ics_flip,
4225                &ticking);
4226
4227     if (n < 21) {
4228         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4229         DisplayError(str, 0);
4230         return;
4231     }
4232
4233     /* Convert the move number to internal form */
4234     moveNum = (moveNum - 1) * 2;
4235     if (to_play == 'B') moveNum++;
4236     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4237       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4238                         0, 1);
4239       return;
4240     }
4241
4242     switch (relation) {
4243       case RELATION_OBSERVING_PLAYED:
4244       case RELATION_OBSERVING_STATIC:
4245         if (gamenum == -1) {
4246             /* Old ICC buglet */
4247             relation = RELATION_OBSERVING_STATIC;
4248         }
4249         newGameMode = IcsObserving;
4250         break;
4251       case RELATION_PLAYING_MYMOVE:
4252       case RELATION_PLAYING_NOTMYMOVE:
4253         newGameMode =
4254           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4255             IcsPlayingWhite : IcsPlayingBlack;
4256         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4257         break;
4258       case RELATION_EXAMINING:
4259         newGameMode = IcsExamining;
4260         break;
4261       case RELATION_ISOLATED_BOARD:
4262       default:
4263         /* Just display this board.  If user was doing something else,
4264            we will forget about it until the next board comes. */
4265         newGameMode = IcsIdle;
4266         break;
4267       case RELATION_STARTING_POSITION:
4268         newGameMode = gameMode;
4269         break;
4270     }
4271
4272     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4273         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4274          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4275       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4276       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4277       static int lastBgGame = -1;
4278       char *toSqr;
4279       for (k = 0; k < ranks; k++) {
4280         for (j = 0; j < files; j++)
4281           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4282         if(gameInfo.holdingsWidth > 1) {
4283              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4284              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4285         }
4286       }
4287       CopyBoard(partnerBoard, board);
4288       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4289         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4290         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4291       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4292       if(toSqr = strchr(str, '-')) {
4293         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4294         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4295       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4296       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4297       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4298       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4299       if(twoBoards) {
4300           DisplayWhiteClock(white_time*fac, to_play == 'W');
4301           DisplayBlackClock(black_time*fac, to_play != 'W');
4302           activePartner = to_play;
4303           if(gamenum != lastBgGame) {
4304               char buf[MSG_SIZ];
4305               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4306               DisplayTitle(buf);
4307           }
4308           lastBgGame = gamenum;
4309           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4310                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4311       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4312                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4313       DisplayMessage(partnerStatus, "");
4314         partnerBoardValid = TRUE;
4315       return;
4316     }
4317
4318     if(appData.dualBoard && appData.bgObserve) {
4319         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4320             SendToICS(ics_prefix), SendToICS("pobserve\n");
4321         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4322             char buf[MSG_SIZ];
4323             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4324             SendToICS(buf);
4325         }
4326     }
4327
4328     /* Modify behavior for initial board display on move listing
4329        of wild games.
4330        */
4331     switch (ics_getting_history) {
4332       case H_FALSE:
4333       case H_REQUESTED:
4334         break;
4335       case H_GOT_REQ_HEADER:
4336       case H_GOT_UNREQ_HEADER:
4337         /* This is the initial position of the current game */
4338         gamenum = ics_gamenum;
4339         moveNum = 0;            /* old ICS bug workaround */
4340         if (to_play == 'B') {
4341           startedFromSetupPosition = TRUE;
4342           blackPlaysFirst = TRUE;
4343           moveNum = 1;
4344           if (forwardMostMove == 0) forwardMostMove = 1;
4345           if (backwardMostMove == 0) backwardMostMove = 1;
4346           if (currentMove == 0) currentMove = 1;
4347         }
4348         newGameMode = gameMode;
4349         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4350         break;
4351       case H_GOT_UNWANTED_HEADER:
4352         /* This is an initial board that we don't want */
4353         return;
4354       case H_GETTING_MOVES:
4355         /* Should not happen */
4356         DisplayError(_("Error gathering move list: extra board"), 0);
4357         ics_getting_history = H_FALSE;
4358         return;
4359     }
4360
4361    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4362                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4363                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4364      /* [HGM] We seem to have switched variant unexpectedly
4365       * Try to guess new variant from board size
4366       */
4367           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4368           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4369           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4370           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4371           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4372           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4373           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4374           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4375           /* Get a move list just to see the header, which
4376              will tell us whether this is really bug or zh */
4377           if (ics_getting_history == H_FALSE) {
4378             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4379             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4380             SendToICS(str);
4381           }
4382     }
4383
4384     /* Take action if this is the first board of a new game, or of a
4385        different game than is currently being displayed.  */
4386     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4387         relation == RELATION_ISOLATED_BOARD) {
4388
4389         /* Forget the old game and get the history (if any) of the new one */
4390         if (gameMode != BeginningOfGame) {
4391           Reset(TRUE, TRUE);
4392         }
4393         newGame = TRUE;
4394         if (appData.autoRaiseBoard) BoardToTop();
4395         prevMove = -3;
4396         if (gamenum == -1) {
4397             newGameMode = IcsIdle;
4398         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4399                    appData.getMoveList && !reqFlag) {
4400             /* Need to get game history */
4401             ics_getting_history = H_REQUESTED;
4402             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4403             SendToICS(str);
4404         }
4405
4406         /* Initially flip the board to have black on the bottom if playing
4407            black or if the ICS flip flag is set, but let the user change
4408            it with the Flip View button. */
4409         flipView = appData.autoFlipView ?
4410           (newGameMode == IcsPlayingBlack) || ics_flip :
4411           appData.flipView;
4412
4413         /* Done with values from previous mode; copy in new ones */
4414         gameMode = newGameMode;
4415         ModeHighlight();
4416         ics_gamenum = gamenum;
4417         if (gamenum == gs_gamenum) {
4418             int klen = strlen(gs_kind);
4419             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4420             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4421             gameInfo.event = StrSave(str);
4422         } else {
4423             gameInfo.event = StrSave("ICS game");
4424         }
4425         gameInfo.site = StrSave(appData.icsHost);
4426         gameInfo.date = PGNDate();
4427         gameInfo.round = StrSave("-");
4428         gameInfo.white = StrSave(white);
4429         gameInfo.black = StrSave(black);
4430         timeControl = basetime * 60 * 1000;
4431         timeControl_2 = 0;
4432         timeIncrement = increment * 1000;
4433         movesPerSession = 0;
4434         gameInfo.timeControl = TimeControlTagValue();
4435         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4436   if (appData.debugMode) {
4437     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4438     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4439     setbuf(debugFP, NULL);
4440   }
4441
4442         gameInfo.outOfBook = NULL;
4443
4444         /* Do we have the ratings? */
4445         if (strcmp(player1Name, white) == 0 &&
4446             strcmp(player2Name, black) == 0) {
4447             if (appData.debugMode)
4448               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4449                       player1Rating, player2Rating);
4450             gameInfo.whiteRating = player1Rating;
4451             gameInfo.blackRating = player2Rating;
4452         } else if (strcmp(player2Name, white) == 0 &&
4453                    strcmp(player1Name, black) == 0) {
4454             if (appData.debugMode)
4455               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4456                       player2Rating, player1Rating);
4457             gameInfo.whiteRating = player2Rating;
4458             gameInfo.blackRating = player1Rating;
4459         }
4460         player1Name[0] = player2Name[0] = NULLCHAR;
4461
4462         /* Silence shouts if requested */
4463         if (appData.quietPlay &&
4464             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4465             SendToICS(ics_prefix);
4466             SendToICS("set shout 0\n");
4467         }
4468     }
4469
4470     /* Deal with midgame name changes */
4471     if (!newGame) {
4472         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4473             if (gameInfo.white) free(gameInfo.white);
4474             gameInfo.white = StrSave(white);
4475         }
4476         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4477             if (gameInfo.black) free(gameInfo.black);
4478             gameInfo.black = StrSave(black);
4479         }
4480     }
4481
4482     /* Throw away game result if anything actually changes in examine mode */
4483     if (gameMode == IcsExamining && !newGame) {
4484         gameInfo.result = GameUnfinished;
4485         if (gameInfo.resultDetails != NULL) {
4486             free(gameInfo.resultDetails);
4487             gameInfo.resultDetails = NULL;
4488         }
4489     }
4490
4491     /* In pausing && IcsExamining mode, we ignore boards coming
4492        in if they are in a different variation than we are. */
4493     if (pauseExamInvalid) return;
4494     if (pausing && gameMode == IcsExamining) {
4495         if (moveNum <= pauseExamForwardMostMove) {
4496             pauseExamInvalid = TRUE;
4497             forwardMostMove = pauseExamForwardMostMove;
4498             return;
4499         }
4500     }
4501
4502   if (appData.debugMode) {
4503     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4504   }
4505     /* Parse the board */
4506     for (k = 0; k < ranks; k++) {
4507       for (j = 0; j < files; j++)
4508         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4509       if(gameInfo.holdingsWidth > 1) {
4510            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4511            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4512       }
4513     }
4514     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4515       board[5][BOARD_RGHT+1] = WhiteAngel;
4516       board[6][BOARD_RGHT+1] = WhiteMarshall;
4517       board[1][0] = BlackMarshall;
4518       board[2][0] = BlackAngel;
4519       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4520     }
4521     CopyBoard(boards[moveNum], board);
4522     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4523     if (moveNum == 0) {
4524         startedFromSetupPosition =
4525           !CompareBoards(board, initialPosition);
4526         if(startedFromSetupPosition)
4527             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4528     }
4529
4530     /* [HGM] Set castling rights. Take the outermost Rooks,
4531        to make it also work for FRC opening positions. Note that board12
4532        is really defective for later FRC positions, as it has no way to
4533        indicate which Rook can castle if they are on the same side of King.
4534        For the initial position we grant rights to the outermost Rooks,
4535        and remember thos rights, and we then copy them on positions
4536        later in an FRC game. This means WB might not recognize castlings with
4537        Rooks that have moved back to their original position as illegal,
4538        but in ICS mode that is not its job anyway.
4539     */
4540     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4541     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4542
4543         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4544             if(board[0][i] == WhiteRook) j = i;
4545         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4546         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4547             if(board[0][i] == WhiteRook) j = i;
4548         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4549         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4550             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4551         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4552         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4553             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4554         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4555
4556         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4557         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4558         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4559             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4560         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4561             if(board[BOARD_HEIGHT-1][k] == bKing)
4562                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4563         if(gameInfo.variant == VariantTwoKings) {
4564             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4565             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4566             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4567         }
4568     } else { int r;
4569         r = boards[moveNum][CASTLING][0] = initialRights[0];
4570         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4571         r = boards[moveNum][CASTLING][1] = initialRights[1];
4572         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4573         r = boards[moveNum][CASTLING][3] = initialRights[3];
4574         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4575         r = boards[moveNum][CASTLING][4] = initialRights[4];
4576         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4577         /* wildcastle kludge: always assume King has rights */
4578         r = boards[moveNum][CASTLING][2] = initialRights[2];
4579         r = boards[moveNum][CASTLING][5] = initialRights[5];
4580     }
4581     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4582     boards[moveNum][EP_STATUS] = EP_NONE;
4583     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4584     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4585     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4586
4587
4588     if (ics_getting_history == H_GOT_REQ_HEADER ||
4589         ics_getting_history == H_GOT_UNREQ_HEADER) {
4590         /* This was an initial position from a move list, not
4591            the current position */
4592         return;
4593     }
4594
4595     /* Update currentMove and known move number limits */
4596     newMove = newGame || moveNum > forwardMostMove;
4597
4598     if (newGame) {
4599         forwardMostMove = backwardMostMove = currentMove = moveNum;
4600         if (gameMode == IcsExamining && moveNum == 0) {
4601           /* Workaround for ICS limitation: we are not told the wild
4602              type when starting to examine a game.  But if we ask for
4603              the move list, the move list header will tell us */
4604             ics_getting_history = H_REQUESTED;
4605             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4606             SendToICS(str);
4607         }
4608     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4609                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4610 #if ZIPPY
4611         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4612         /* [HGM] applied this also to an engine that is silently watching        */
4613         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4614             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4615             gameInfo.variant == currentlyInitializedVariant) {
4616           takeback = forwardMostMove - moveNum;
4617           for (i = 0; i < takeback; i++) {
4618             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4619             SendToProgram("undo\n", &first);
4620           }
4621         }
4622 #endif
4623
4624         forwardMostMove = moveNum;
4625         if (!pausing || currentMove > forwardMostMove)
4626           currentMove = forwardMostMove;
4627     } else {
4628         /* New part of history that is not contiguous with old part */
4629         if (pausing && gameMode == IcsExamining) {
4630             pauseExamInvalid = TRUE;
4631             forwardMostMove = pauseExamForwardMostMove;
4632             return;
4633         }
4634         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4635 #if ZIPPY
4636             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4637                 // [HGM] when we will receive the move list we now request, it will be
4638                 // fed to the engine from the first move on. So if the engine is not
4639                 // in the initial position now, bring it there.
4640                 InitChessProgram(&first, 0);
4641             }
4642 #endif
4643             ics_getting_history = H_REQUESTED;
4644             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4645             SendToICS(str);
4646         }
4647         forwardMostMove = backwardMostMove = currentMove = moveNum;
4648     }
4649
4650     /* Update the clocks */
4651     if (strchr(elapsed_time, '.')) {
4652       /* Time is in ms */
4653       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4654       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4655     } else {
4656       /* Time is in seconds */
4657       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4658       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4659     }
4660
4661
4662 #if ZIPPY
4663     if (appData.zippyPlay && newGame &&
4664         gameMode != IcsObserving && gameMode != IcsIdle &&
4665         gameMode != IcsExamining)
4666       ZippyFirstBoard(moveNum, basetime, increment);
4667 #endif
4668
4669     /* Put the move on the move list, first converting
4670        to canonical algebraic form. */
4671     if (moveNum > 0) {
4672   if (appData.debugMode) {
4673     if (appData.debugMode) { int f = forwardMostMove;
4674         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4675                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4676                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4677     }
4678     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4679     fprintf(debugFP, "moveNum = %d\n", moveNum);
4680     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4681     setbuf(debugFP, NULL);
4682   }
4683         if (moveNum <= backwardMostMove) {
4684             /* We don't know what the board looked like before
4685                this move.  Punt. */
4686           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4687             strcat(parseList[moveNum - 1], " ");
4688             strcat(parseList[moveNum - 1], elapsed_time);
4689             moveList[moveNum - 1][0] = NULLCHAR;
4690         } else if (strcmp(move_str, "none") == 0) {
4691             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4692             /* Again, we don't know what the board looked like;
4693                this is really the start of the game. */
4694             parseList[moveNum - 1][0] = NULLCHAR;
4695             moveList[moveNum - 1][0] = NULLCHAR;
4696             backwardMostMove = moveNum;
4697             startedFromSetupPosition = TRUE;
4698             fromX = fromY = toX = toY = -1;
4699         } else {
4700           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4701           //                 So we parse the long-algebraic move string in stead of the SAN move
4702           int valid; char buf[MSG_SIZ], *prom;
4703
4704           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4705                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4706           // str looks something like "Q/a1-a2"; kill the slash
4707           if(str[1] == '/')
4708             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4709           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4710           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4711                 strcat(buf, prom); // long move lacks promo specification!
4712           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4713                 if(appData.debugMode)
4714                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4715                 safeStrCpy(move_str, buf, MSG_SIZ);
4716           }
4717           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4718                                 &fromX, &fromY, &toX, &toY, &promoChar)
4719                || ParseOneMove(buf, moveNum - 1, &moveType,
4720                                 &fromX, &fromY, &toX, &toY, &promoChar);
4721           // end of long SAN patch
4722           if (valid) {
4723             (void) CoordsToAlgebraic(boards[moveNum - 1],
4724                                      PosFlags(moveNum - 1),
4725                                      fromY, fromX, toY, toX, promoChar,
4726                                      parseList[moveNum-1]);
4727             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4728               case MT_NONE:
4729               case MT_STALEMATE:
4730               default:
4731                 break;
4732               case MT_CHECK:
4733                 if(gameInfo.variant != VariantShogi)
4734                     strcat(parseList[moveNum - 1], "+");
4735                 break;
4736               case MT_CHECKMATE:
4737               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4738                 strcat(parseList[moveNum - 1], "#");
4739                 break;
4740             }
4741             strcat(parseList[moveNum - 1], " ");
4742             strcat(parseList[moveNum - 1], elapsed_time);
4743             /* currentMoveString is set as a side-effect of ParseOneMove */
4744             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4745             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4746             strcat(moveList[moveNum - 1], "\n");
4747
4748             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4749                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4750               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4751                 ChessSquare old, new = boards[moveNum][k][j];
4752                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4753                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4754                   if(old == new) continue;
4755                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4756                   else if(new == WhiteWazir || new == BlackWazir) {
4757                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4758                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4759                       else boards[moveNum][k][j] = old; // preserve type of Gold
4760                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4761                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4762               }
4763           } else {
4764             /* Move from ICS was illegal!?  Punt. */
4765             if (appData.debugMode) {
4766               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4767               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4768             }
4769             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4770             strcat(parseList[moveNum - 1], " ");
4771             strcat(parseList[moveNum - 1], elapsed_time);
4772             moveList[moveNum - 1][0] = NULLCHAR;
4773             fromX = fromY = toX = toY = -1;
4774           }
4775         }
4776   if (appData.debugMode) {
4777     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4778     setbuf(debugFP, NULL);
4779   }
4780
4781 #if ZIPPY
4782         /* Send move to chess program (BEFORE animating it). */
4783         if (appData.zippyPlay && !newGame && newMove &&
4784            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4785
4786             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4787                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4788                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4789                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4790                             move_str);
4791                     DisplayError(str, 0);
4792                 } else {
4793                     if (first.sendTime) {
4794                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4795                     }
4796                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4797                     if (firstMove && !bookHit) {
4798                         firstMove = FALSE;
4799                         if (first.useColors) {
4800                           SendToProgram(gameMode == IcsPlayingWhite ?
4801                                         "white\ngo\n" :
4802                                         "black\ngo\n", &first);
4803                         } else {
4804                           SendToProgram("go\n", &first);
4805                         }
4806                         first.maybeThinking = TRUE;
4807                     }
4808                 }
4809             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4810               if (moveList[moveNum - 1][0] == NULLCHAR) {
4811                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4812                 DisplayError(str, 0);
4813               } else {
4814                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4815                 SendMoveToProgram(moveNum - 1, &first);
4816               }
4817             }
4818         }
4819 #endif
4820     }
4821
4822     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4823         /* If move comes from a remote source, animate it.  If it
4824            isn't remote, it will have already been animated. */
4825         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4826             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4827         }
4828         if (!pausing && appData.highlightLastMove) {
4829             SetHighlights(fromX, fromY, toX, toY);
4830         }
4831     }
4832
4833     /* Start the clocks */
4834     whiteFlag = blackFlag = FALSE;
4835     appData.clockMode = !(basetime == 0 && increment == 0);
4836     if (ticking == 0) {
4837       ics_clock_paused = TRUE;
4838       StopClocks();
4839     } else if (ticking == 1) {
4840       ics_clock_paused = FALSE;
4841     }
4842     if (gameMode == IcsIdle ||
4843         relation == RELATION_OBSERVING_STATIC ||
4844         relation == RELATION_EXAMINING ||
4845         ics_clock_paused)
4846       DisplayBothClocks();
4847     else
4848       StartClocks();
4849
4850     /* Display opponents and material strengths */
4851     if (gameInfo.variant != VariantBughouse &&
4852         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4853         if (tinyLayout || smallLayout) {
4854             if(gameInfo.variant == VariantNormal)
4855               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4856                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4857                     basetime, increment);
4858             else
4859               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4860                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4861                     basetime, increment, (int) gameInfo.variant);
4862         } else {
4863             if(gameInfo.variant == VariantNormal)
4864               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4865                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4866                     basetime, increment);
4867             else
4868               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4869                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4870                     basetime, increment, VariantName(gameInfo.variant));
4871         }
4872         DisplayTitle(str);
4873   if (appData.debugMode) {
4874     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4875   }
4876     }
4877
4878
4879     /* Display the board */
4880     if (!pausing && !appData.noGUI) {
4881
4882       if (appData.premove)
4883           if (!gotPremove ||
4884              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4885              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4886               ClearPremoveHighlights();
4887
4888       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4889         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4890       DrawPosition(j, boards[currentMove]);
4891
4892       DisplayMove(moveNum - 1);
4893       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4894             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4895               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4896         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4897       }
4898     }
4899
4900     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4901 #if ZIPPY
4902     if(bookHit) { // [HGM] book: simulate book reply
4903         static char bookMove[MSG_SIZ]; // a bit generous?
4904
4905         programStats.nodes = programStats.depth = programStats.time =
4906         programStats.score = programStats.got_only_move = 0;
4907         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4908
4909         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4910         strcat(bookMove, bookHit);
4911         HandleMachineMove(bookMove, &first);
4912     }
4913 #endif
4914 }
4915
4916 void
4917 GetMoveListEvent ()
4918 {
4919     char buf[MSG_SIZ];
4920     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4921         ics_getting_history = H_REQUESTED;
4922         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4923         SendToICS(buf);
4924     }
4925 }
4926
4927 void
4928 SendToBoth (char *msg)
4929 {   // to make it easy to keep two engines in step in dual analysis
4930     SendToProgram(msg, &first);
4931     if(second.analyzing) SendToProgram(msg, &second);
4932 }
4933
4934 void
4935 AnalysisPeriodicEvent (int force)
4936 {
4937     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4938          && !force) || !appData.periodicUpdates)
4939       return;
4940
4941     /* Send . command to Crafty to collect stats */
4942     SendToBoth(".\n");
4943
4944     /* Don't send another until we get a response (this makes
4945        us stop sending to old Crafty's which don't understand
4946        the "." command (sending illegal cmds resets node count & time,
4947        which looks bad)) */
4948     programStats.ok_to_send = 0;
4949 }
4950
4951 void
4952 ics_update_width (int new_width)
4953 {
4954         ics_printf("set width %d\n", new_width);
4955 }
4956
4957 void
4958 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4959 {
4960     char buf[MSG_SIZ];
4961
4962     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4963         // null move in variant where engine does not understand it (for analysis purposes)
4964         SendBoard(cps, moveNum + 1); // send position after move in stead.
4965         return;
4966     }
4967     if (cps->useUsermove) {
4968       SendToProgram("usermove ", cps);
4969     }
4970     if (cps->useSAN) {
4971       char *space;
4972       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4973         int len = space - parseList[moveNum];
4974         memcpy(buf, parseList[moveNum], len);
4975         buf[len++] = '\n';
4976         buf[len] = NULLCHAR;
4977       } else {
4978         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4979       }
4980       SendToProgram(buf, cps);
4981     } else {
4982       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4983         AlphaRank(moveList[moveNum], 4);
4984         SendToProgram(moveList[moveNum], cps);
4985         AlphaRank(moveList[moveNum], 4); // and back
4986       } else
4987       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4988        * the engine. It would be nice to have a better way to identify castle
4989        * moves here. */
4990       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4991                                                                          && cps->useOOCastle) {
4992         int fromX = moveList[moveNum][0] - AAA;
4993         int fromY = moveList[moveNum][1] - ONE;
4994         int toX = moveList[moveNum][2] - AAA;
4995         int toY = moveList[moveNum][3] - ONE;
4996         if((boards[moveNum][fromY][fromX] == WhiteKing
4997             && boards[moveNum][toY][toX] == WhiteRook)
4998            || (boards[moveNum][fromY][fromX] == BlackKing
4999                && boards[moveNum][toY][toX] == BlackRook)) {
5000           if(toX > fromX) SendToProgram("O-O\n", cps);
5001           else SendToProgram("O-O-O\n", cps);
5002         }
5003         else SendToProgram(moveList[moveNum], cps);
5004       } else
5005       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5006         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5007           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5008           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5009                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5010         } else
5011           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5012                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5013         SendToProgram(buf, cps);
5014       }
5015       else SendToProgram(moveList[moveNum], cps);
5016       /* End of additions by Tord */
5017     }
5018
5019     /* [HGM] setting up the opening has brought engine in force mode! */
5020     /*       Send 'go' if we are in a mode where machine should play. */
5021     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5022         (gameMode == TwoMachinesPlay   ||
5023 #if ZIPPY
5024          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5025 #endif
5026          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5027         SendToProgram("go\n", cps);
5028   if (appData.debugMode) {
5029     fprintf(debugFP, "(extra)\n");
5030   }
5031     }
5032     setboardSpoiledMachineBlack = 0;
5033 }
5034
5035 void
5036 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5037 {
5038     char user_move[MSG_SIZ];
5039     char suffix[4];
5040
5041     if(gameInfo.variant == VariantSChess && promoChar) {
5042         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5043         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5044     } else suffix[0] = NULLCHAR;
5045
5046     switch (moveType) {
5047       default:
5048         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5049                 (int)moveType, fromX, fromY, toX, toY);
5050         DisplayError(user_move + strlen("say "), 0);
5051         break;
5052       case WhiteKingSideCastle:
5053       case BlackKingSideCastle:
5054       case WhiteQueenSideCastleWild:
5055       case BlackQueenSideCastleWild:
5056       /* PUSH Fabien */
5057       case WhiteHSideCastleFR:
5058       case BlackHSideCastleFR:
5059       /* POP Fabien */
5060         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5061         break;
5062       case WhiteQueenSideCastle:
5063       case BlackQueenSideCastle:
5064       case WhiteKingSideCastleWild:
5065       case BlackKingSideCastleWild:
5066       /* PUSH Fabien */
5067       case WhiteASideCastleFR:
5068       case BlackASideCastleFR:
5069       /* POP Fabien */
5070         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5071         break;
5072       case WhiteNonPromotion:
5073       case BlackNonPromotion:
5074         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5075         break;
5076       case WhitePromotion:
5077       case BlackPromotion:
5078         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5079           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5080                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5081                 PieceToChar(WhiteFerz));
5082         else if(gameInfo.variant == VariantGreat)
5083           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5084                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5085                 PieceToChar(WhiteMan));
5086         else
5087           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5089                 promoChar);
5090         break;
5091       case WhiteDrop:
5092       case BlackDrop:
5093       drop:
5094         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5095                  ToUpper(PieceToChar((ChessSquare) fromX)),
5096                  AAA + toX, ONE + toY);
5097         break;
5098       case IllegalMove:  /* could be a variant we don't quite understand */
5099         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5100       case NormalMove:
5101       case WhiteCapturesEnPassant:
5102       case BlackCapturesEnPassant:
5103         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5104                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5105         break;
5106     }
5107     SendToICS(user_move);
5108     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5109         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5110 }
5111
5112 void
5113 UploadGameEvent ()
5114 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5115     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5116     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5117     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5118       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5119       return;
5120     }
5121     if(gameMode != IcsExamining) { // is this ever not the case?
5122         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5123
5124         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5125           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5126         } else { // on FICS we must first go to general examine mode
5127           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5128         }
5129         if(gameInfo.variant != VariantNormal) {
5130             // try figure out wild number, as xboard names are not always valid on ICS
5131             for(i=1; i<=36; i++) {
5132               snprintf(buf, MSG_SIZ, "wild/%d", i);
5133                 if(StringToVariant(buf) == gameInfo.variant) break;
5134             }
5135             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5136             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5137             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5138         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5139         SendToICS(ics_prefix);
5140         SendToICS(buf);
5141         if(startedFromSetupPosition || backwardMostMove != 0) {
5142           fen = PositionToFEN(backwardMostMove, NULL);
5143           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5144             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5145             SendToICS(buf);
5146           } else { // FICS: everything has to set by separate bsetup commands
5147             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5148             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5149             SendToICS(buf);
5150             if(!WhiteOnMove(backwardMostMove)) {
5151                 SendToICS("bsetup tomove black\n");
5152             }
5153             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5154             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5155             SendToICS(buf);
5156             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5157             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5158             SendToICS(buf);
5159             i = boards[backwardMostMove][EP_STATUS];
5160             if(i >= 0) { // set e.p.
5161               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5162                 SendToICS(buf);
5163             }
5164             bsetup++;
5165           }
5166         }
5167       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5168             SendToICS("bsetup done\n"); // switch to normal examining.
5169     }
5170     for(i = backwardMostMove; i<last; i++) {
5171         char buf[20];
5172         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5173         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5174             int len = strlen(moveList[i]);
5175             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5176             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5177         }
5178         SendToICS(buf);
5179     }
5180     SendToICS(ics_prefix);
5181     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5182 }
5183
5184 void
5185 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5186 {
5187     if (rf == DROP_RANK) {
5188       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5189       sprintf(move, "%c@%c%c\n",
5190                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5191     } else {
5192         if (promoChar == 'x' || promoChar == NULLCHAR) {
5193           sprintf(move, "%c%c%c%c\n",
5194                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5195         } else {
5196             sprintf(move, "%c%c%c%c%c\n",
5197                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5198         }
5199     }
5200 }
5201
5202 void
5203 ProcessICSInitScript (FILE *f)
5204 {
5205     char buf[MSG_SIZ];
5206
5207     while (fgets(buf, MSG_SIZ, f)) {
5208         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5209     }
5210
5211     fclose(f);
5212 }
5213
5214
5215 static int lastX, lastY, selectFlag, dragging;
5216
5217 void
5218 Sweep (int step)
5219 {
5220     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5221     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5222     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5223     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5224     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5225     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5226     do {
5227         promoSweep -= step;
5228         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5229         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5230         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5231         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5232         if(!step) step = -1;
5233     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5234             appData.testLegality && (promoSweep == king ||
5235             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5236     if(toX >= 0) {
5237         int victim = boards[currentMove][toY][toX];
5238         boards[currentMove][toY][toX] = promoSweep;
5239         DrawPosition(FALSE, boards[currentMove]);
5240         boards[currentMove][toY][toX] = victim;
5241     } else
5242     ChangeDragPiece(promoSweep);
5243 }
5244
5245 int
5246 PromoScroll (int x, int y)
5247 {
5248   int step = 0;
5249
5250   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5251   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5252   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5253   if(!step) return FALSE;
5254   lastX = x; lastY = y;
5255   if((promoSweep < BlackPawn) == flipView) step = -step;
5256   if(step > 0) selectFlag = 1;
5257   if(!selectFlag) Sweep(step);
5258   return FALSE;
5259 }
5260
5261 void
5262 NextPiece (int step)
5263 {
5264     ChessSquare piece = boards[currentMove][toY][toX];
5265     do {
5266         pieceSweep -= step;
5267         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5268         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5269         if(!step) step = -1;
5270     } while(PieceToChar(pieceSweep) == '.');
5271     boards[currentMove][toY][toX] = pieceSweep;
5272     DrawPosition(FALSE, boards[currentMove]);
5273     boards[currentMove][toY][toX] = piece;
5274 }
5275 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5276 void
5277 AlphaRank (char *move, int n)
5278 {
5279 //    char *p = move, c; int x, y;
5280
5281     if (appData.debugMode) {
5282         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5283     }
5284
5285     if(move[1]=='*' &&
5286        move[2]>='0' && move[2]<='9' &&
5287        move[3]>='a' && move[3]<='x'    ) {
5288         move[1] = '@';
5289         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5290         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5291     } else
5292     if(move[0]>='0' && move[0]<='9' &&
5293        move[1]>='a' && move[1]<='x' &&
5294        move[2]>='0' && move[2]<='9' &&
5295        move[3]>='a' && move[3]<='x'    ) {
5296         /* input move, Shogi -> normal */
5297         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5298         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5299         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5300         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5301     } else
5302     if(move[1]=='@' &&
5303        move[3]>='0' && move[3]<='9' &&
5304        move[2]>='a' && move[2]<='x'    ) {
5305         move[1] = '*';
5306         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5307         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5308     } else
5309     if(
5310        move[0]>='a' && move[0]<='x' &&
5311        move[3]>='0' && move[3]<='9' &&
5312        move[2]>='a' && move[2]<='x'    ) {
5313          /* output move, normal -> Shogi */
5314         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5315         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5316         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5317         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5318         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5319     }
5320     if (appData.debugMode) {
5321         fprintf(debugFP, "   out = '%s'\n", move);
5322     }
5323 }
5324
5325 char yy_textstr[8000];
5326
5327 /* Parser for moves from gnuchess, ICS, or user typein box */
5328 Boolean
5329 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5330 {
5331     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5332
5333     switch (*moveType) {
5334       case WhitePromotion:
5335       case BlackPromotion:
5336       case WhiteNonPromotion:
5337       case BlackNonPromotion:
5338       case NormalMove:
5339       case WhiteCapturesEnPassant:
5340       case BlackCapturesEnPassant:
5341       case WhiteKingSideCastle:
5342       case WhiteQueenSideCastle:
5343       case BlackKingSideCastle:
5344       case BlackQueenSideCastle:
5345       case WhiteKingSideCastleWild:
5346       case WhiteQueenSideCastleWild:
5347       case BlackKingSideCastleWild:
5348       case BlackQueenSideCastleWild:
5349       /* Code added by Tord: */
5350       case WhiteHSideCastleFR:
5351       case WhiteASideCastleFR:
5352       case BlackHSideCastleFR:
5353       case BlackASideCastleFR:
5354       /* End of code added by Tord */
5355       case IllegalMove:         /* bug or odd chess variant */
5356         *fromX = currentMoveString[0] - AAA;
5357         *fromY = currentMoveString[1] - ONE;
5358         *toX = currentMoveString[2] - AAA;
5359         *toY = currentMoveString[3] - ONE;
5360         *promoChar = currentMoveString[4];
5361         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5362             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5363     if (appData.debugMode) {
5364         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5365     }
5366             *fromX = *fromY = *toX = *toY = 0;
5367             return FALSE;
5368         }
5369         if (appData.testLegality) {
5370           return (*moveType != IllegalMove);
5371         } else {
5372           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5373                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5374         }
5375
5376       case WhiteDrop:
5377       case BlackDrop:
5378         *fromX = *moveType == WhiteDrop ?
5379           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5380           (int) CharToPiece(ToLower(currentMoveString[0]));
5381         *fromY = DROP_RANK;
5382         *toX = currentMoveString[2] - AAA;
5383         *toY = currentMoveString[3] - ONE;
5384         *promoChar = NULLCHAR;
5385         return TRUE;
5386
5387       case AmbiguousMove:
5388       case ImpossibleMove:
5389       case EndOfFile:
5390       case ElapsedTime:
5391       case Comment:
5392       case PGNTag:
5393       case NAG:
5394       case WhiteWins:
5395       case BlackWins:
5396       case GameIsDrawn:
5397       default:
5398     if (appData.debugMode) {
5399         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5400     }
5401         /* bug? */
5402         *fromX = *fromY = *toX = *toY = 0;
5403         *promoChar = NULLCHAR;
5404         return FALSE;
5405     }
5406 }
5407
5408 Boolean pushed = FALSE;
5409 char *lastParseAttempt;
5410
5411 void
5412 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5413 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5414   int fromX, fromY, toX, toY; char promoChar;
5415   ChessMove moveType;
5416   Boolean valid;
5417   int nr = 0;
5418
5419   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5420   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5421     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5422     pushed = TRUE;
5423   }
5424   endPV = forwardMostMove;
5425   do {
5426     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5427     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5428     lastParseAttempt = pv;
5429     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5430     if(!valid && nr == 0 &&
5431        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5432         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5433         // Hande case where played move is different from leading PV move
5434         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5435         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5436         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5437         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5438           endPV += 2; // if position different, keep this
5439           moveList[endPV-1][0] = fromX + AAA;
5440           moveList[endPV-1][1] = fromY + ONE;
5441           moveList[endPV-1][2] = toX + AAA;
5442           moveList[endPV-1][3] = toY + ONE;
5443           parseList[endPV-1][0] = NULLCHAR;
5444           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5445         }
5446       }
5447     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5448     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5449     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5450     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5451         valid++; // allow comments in PV
5452         continue;
5453     }
5454     nr++;
5455     if(endPV+1 > framePtr) break; // no space, truncate
5456     if(!valid) break;
5457     endPV++;
5458     CopyBoard(boards[endPV], boards[endPV-1]);
5459     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5460     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5461     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5462     CoordsToAlgebraic(boards[endPV - 1],
5463                              PosFlags(endPV - 1),
5464                              fromY, fromX, toY, toX, promoChar,
5465                              parseList[endPV - 1]);
5466   } while(valid);
5467   if(atEnd == 2) return; // used hidden, for PV conversion
5468   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5469   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5470   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5471                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5472   DrawPosition(TRUE, boards[currentMove]);
5473 }
5474
5475 int
5476 MultiPV (ChessProgramState *cps)
5477 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5478         int i;
5479         for(i=0; i<cps->nrOptions; i++)
5480             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5481                 return i;
5482         return -1;
5483 }
5484
5485 Boolean
5486 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5487 {
5488         int startPV, multi, lineStart, origIndex = index;
5489         char *p, buf2[MSG_SIZ];
5490         ChessProgramState *cps = (pane ? &second : &first);
5491
5492         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5493         lastX = x; lastY = y;
5494         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5495         lineStart = startPV = index;
5496         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5497         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5498         index = startPV;
5499         do{ while(buf[index] && buf[index] != '\n') index++;
5500         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5501         buf[index] = 0;
5502         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5503                 int n = cps->option[multi].value;
5504                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5505                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5506                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5507                 cps->option[multi].value = n;
5508                 *start = *end = 0;
5509                 return FALSE;
5510         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5511                 ExcludeClick(origIndex - lineStart);
5512                 return FALSE;
5513         }
5514         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5515         *start = startPV; *end = index-1;
5516         return TRUE;
5517 }
5518
5519 char *
5520 PvToSAN (char *pv)
5521 {
5522         static char buf[10*MSG_SIZ];
5523         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5524         *buf = NULLCHAR;
5525         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5526         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5527         for(i = forwardMostMove; i<endPV; i++){
5528             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5529             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5530             k += strlen(buf+k);
5531         }
5532         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5533         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5534         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5535         endPV = savedEnd;
5536         return buf;
5537 }
5538
5539 Boolean
5540 LoadPV (int x, int y)
5541 { // called on right mouse click to load PV
5542   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5543   lastX = x; lastY = y;
5544   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5545   return TRUE;
5546 }
5547
5548 void
5549 UnLoadPV ()
5550 {
5551   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5552   if(endPV < 0) return;
5553   if(appData.autoCopyPV) CopyFENToClipboard();
5554   endPV = -1;
5555   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5556         Boolean saveAnimate = appData.animate;
5557         if(pushed) {
5558             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5559                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5560             } else storedGames--; // abandon shelved tail of original game
5561         }
5562         pushed = FALSE;
5563         forwardMostMove = currentMove;
5564         currentMove = oldFMM;
5565         appData.animate = FALSE;
5566         ToNrEvent(forwardMostMove);
5567         appData.animate = saveAnimate;
5568   }
5569   currentMove = forwardMostMove;
5570   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5571   ClearPremoveHighlights();
5572   DrawPosition(TRUE, boards[currentMove]);
5573 }
5574
5575 void
5576 MovePV (int x, int y, int h)
5577 { // step through PV based on mouse coordinates (called on mouse move)
5578   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5579
5580   // we must somehow check if right button is still down (might be released off board!)
5581   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5582   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5583   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5584   if(!step) return;
5585   lastX = x; lastY = y;
5586
5587   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5588   if(endPV < 0) return;
5589   if(y < margin) step = 1; else
5590   if(y > h - margin) step = -1;
5591   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5592   currentMove += step;
5593   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5594   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5595                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5596   DrawPosition(FALSE, boards[currentMove]);
5597 }
5598
5599
5600 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5601 // All positions will have equal probability, but the current method will not provide a unique
5602 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5603 #define DARK 1
5604 #define LITE 2
5605 #define ANY 3
5606
5607 int squaresLeft[4];
5608 int piecesLeft[(int)BlackPawn];
5609 int seed, nrOfShuffles;
5610
5611 void
5612 GetPositionNumber ()
5613 {       // sets global variable seed
5614         int i;
5615
5616         seed = appData.defaultFrcPosition;
5617         if(seed < 0) { // randomize based on time for negative FRC position numbers
5618                 for(i=0; i<50; i++) seed += random();
5619                 seed = random() ^ random() >> 8 ^ random() << 8;
5620                 if(seed<0) seed = -seed;
5621         }
5622 }
5623
5624 int
5625 put (Board board, int pieceType, int rank, int n, int shade)
5626 // put the piece on the (n-1)-th empty squares of the given shade
5627 {
5628         int i;
5629
5630         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5631                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5632                         board[rank][i] = (ChessSquare) pieceType;
5633                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5634                         squaresLeft[ANY]--;
5635                         piecesLeft[pieceType]--;
5636                         return i;
5637                 }
5638         }
5639         return -1;
5640 }
5641
5642
5643 void
5644 AddOnePiece (Board board, int pieceType, int rank, int shade)
5645 // calculate where the next piece goes, (any empty square), and put it there
5646 {
5647         int i;
5648
5649         i = seed % squaresLeft[shade];
5650         nrOfShuffles *= squaresLeft[shade];
5651         seed /= squaresLeft[shade];
5652         put(board, pieceType, rank, i, shade);
5653 }
5654
5655 void
5656 AddTwoPieces (Board board, int pieceType, int rank)
5657 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5658 {
5659         int i, n=squaresLeft[ANY], j=n-1, k;
5660
5661         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5662         i = seed % k;  // pick one
5663         nrOfShuffles *= k;
5664         seed /= k;
5665         while(i >= j) i -= j--;
5666         j = n - 1 - j; i += j;
5667         put(board, pieceType, rank, j, ANY);
5668         put(board, pieceType, rank, i, ANY);
5669 }
5670
5671 void
5672 SetUpShuffle (Board board, int number)
5673 {
5674         int i, p, first=1;
5675
5676         GetPositionNumber(); nrOfShuffles = 1;
5677
5678         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5679         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5680         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5681
5682         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5683
5684         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5685             p = (int) board[0][i];
5686             if(p < (int) BlackPawn) piecesLeft[p] ++;
5687             board[0][i] = EmptySquare;
5688         }
5689
5690         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5691             // shuffles restricted to allow normal castling put KRR first
5692             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5693                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5694             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5695                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5696             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5697                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5698             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5699                 put(board, WhiteRook, 0, 0, ANY);
5700             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5701         }
5702
5703         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5704             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5705             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5706                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5707                 while(piecesLeft[p] >= 2) {
5708                     AddOnePiece(board, p, 0, LITE);
5709                     AddOnePiece(board, p, 0, DARK);
5710                 }
5711                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5712             }
5713
5714         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5715             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5716             // but we leave King and Rooks for last, to possibly obey FRC restriction
5717             if(p == (int)WhiteRook) continue;
5718             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5719             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5720         }
5721
5722         // now everything is placed, except perhaps King (Unicorn) and Rooks
5723
5724         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5725             // Last King gets castling rights
5726             while(piecesLeft[(int)WhiteUnicorn]) {
5727                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5728                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5729             }
5730
5731             while(piecesLeft[(int)WhiteKing]) {
5732                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5733                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5734             }
5735
5736
5737         } else {
5738             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5739             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5740         }
5741
5742         // Only Rooks can be left; simply place them all
5743         while(piecesLeft[(int)WhiteRook]) {
5744                 i = put(board, WhiteRook, 0, 0, ANY);
5745                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5746                         if(first) {
5747                                 first=0;
5748                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5749                         }
5750                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5751                 }
5752         }
5753         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5754             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5755         }
5756
5757         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5758 }
5759
5760 int
5761 SetCharTable (char *table, const char * map)
5762 /* [HGM] moved here from winboard.c because of its general usefulness */
5763 /*       Basically a safe strcpy that uses the last character as King */
5764 {
5765     int result = FALSE; int NrPieces;
5766
5767     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5768                     && NrPieces >= 12 && !(NrPieces&1)) {
5769         int i; /* [HGM] Accept even length from 12 to 34 */
5770
5771         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5772         for( i=0; i<NrPieces/2-1; i++ ) {
5773             table[i] = map[i];
5774             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5775         }
5776         table[(int) WhiteKing]  = map[NrPieces/2-1];
5777         table[(int) BlackKing]  = map[NrPieces-1];
5778
5779         result = TRUE;
5780     }
5781
5782     return result;
5783 }
5784
5785 void
5786 Prelude (Board board)
5787 {       // [HGM] superchess: random selection of exo-pieces
5788         int i, j, k; ChessSquare p;
5789         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5790
5791         GetPositionNumber(); // use FRC position number
5792
5793         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5794             SetCharTable(pieceToChar, appData.pieceToCharTable);
5795             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5796                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5797         }
5798
5799         j = seed%4;                 seed /= 4;
5800         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5801         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5802         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5803         j = seed%3 + (seed%3 >= j); seed /= 3;
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;
5808         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5816         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5817         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5818         put(board, exoPieces[0],    0, 0, ANY);
5819         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5820 }
5821
5822 void
5823 InitPosition (int redraw)
5824 {
5825     ChessSquare (* pieces)[BOARD_FILES];
5826     int i, j, pawnRow, overrule,
5827     oldx = gameInfo.boardWidth,
5828     oldy = gameInfo.boardHeight,
5829     oldh = gameInfo.holdingsWidth;
5830     static int oldv;
5831
5832     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5833
5834     /* [AS] Initialize pv info list [HGM] and game status */
5835     {
5836         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5837             pvInfoList[i].depth = 0;
5838             boards[i][EP_STATUS] = EP_NONE;
5839             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5840         }
5841
5842         initialRulePlies = 0; /* 50-move counter start */
5843
5844         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5845         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5846     }
5847
5848
5849     /* [HGM] logic here is completely changed. In stead of full positions */
5850     /* the initialized data only consist of the two backranks. The switch */
5851     /* selects which one we will use, which is than copied to the Board   */
5852     /* initialPosition, which for the rest is initialized by Pawns and    */
5853     /* empty squares. This initial position is then copied to boards[0],  */
5854     /* possibly after shuffling, so that it remains available.            */
5855
5856     gameInfo.holdingsWidth = 0; /* default board sizes */
5857     gameInfo.boardWidth    = 8;
5858     gameInfo.boardHeight   = 8;
5859     gameInfo.holdingsSize  = 0;
5860     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5861     for(i=0; i<BOARD_FILES-2; i++)
5862       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5863     initialPosition[EP_STATUS] = EP_NONE;
5864     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5865     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5866          SetCharTable(pieceNickName, appData.pieceNickNames);
5867     else SetCharTable(pieceNickName, "............");
5868     pieces = FIDEArray;
5869
5870     switch (gameInfo.variant) {
5871     case VariantFischeRandom:
5872       shuffleOpenings = TRUE;
5873     default:
5874       break;
5875     case VariantShatranj:
5876       pieces = ShatranjArray;
5877       nrCastlingRights = 0;
5878       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5879       break;
5880     case VariantMakruk:
5881       pieces = makrukArray;
5882       nrCastlingRights = 0;
5883       startedFromSetupPosition = TRUE;
5884       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5885       break;
5886     case VariantTwoKings:
5887       pieces = twoKingsArray;
5888       break;
5889     case VariantGrand:
5890       pieces = GrandArray;
5891       nrCastlingRights = 0;
5892       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5893       gameInfo.boardWidth = 10;
5894       gameInfo.boardHeight = 10;
5895       gameInfo.holdingsSize = 7;
5896       break;
5897     case VariantCapaRandom:
5898       shuffleOpenings = TRUE;
5899     case VariantCapablanca:
5900       pieces = CapablancaArray;
5901       gameInfo.boardWidth = 10;
5902       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5903       break;
5904     case VariantGothic:
5905       pieces = GothicArray;
5906       gameInfo.boardWidth = 10;
5907       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5908       break;
5909     case VariantSChess:
5910       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5911       gameInfo.holdingsSize = 7;
5912       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5913       break;
5914     case VariantJanus:
5915       pieces = JanusArray;
5916       gameInfo.boardWidth = 10;
5917       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5918       nrCastlingRights = 6;
5919         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5920         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5921         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5922         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5923         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5924         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5925       break;
5926     case VariantFalcon:
5927       pieces = FalconArray;
5928       gameInfo.boardWidth = 10;
5929       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5930       break;
5931     case VariantXiangqi:
5932       pieces = XiangqiArray;
5933       gameInfo.boardWidth  = 9;
5934       gameInfo.boardHeight = 10;
5935       nrCastlingRights = 0;
5936       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5937       break;
5938     case VariantShogi:
5939       pieces = ShogiArray;
5940       gameInfo.boardWidth  = 9;
5941       gameInfo.boardHeight = 9;
5942       gameInfo.holdingsSize = 7;
5943       nrCastlingRights = 0;
5944       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5945       break;
5946     case VariantCourier:
5947       pieces = CourierArray;
5948       gameInfo.boardWidth  = 12;
5949       nrCastlingRights = 0;
5950       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5951       break;
5952     case VariantKnightmate:
5953       pieces = KnightmateArray;
5954       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5955       break;
5956     case VariantSpartan:
5957       pieces = SpartanArray;
5958       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5959       break;
5960     case VariantFairy:
5961       pieces = fairyArray;
5962       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5963       break;
5964     case VariantGreat:
5965       pieces = GreatArray;
5966       gameInfo.boardWidth = 10;
5967       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5968       gameInfo.holdingsSize = 8;
5969       break;
5970     case VariantSuper:
5971       pieces = FIDEArray;
5972       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5973       gameInfo.holdingsSize = 8;
5974       startedFromSetupPosition = TRUE;
5975       break;
5976     case VariantCrazyhouse:
5977     case VariantBughouse:
5978       pieces = FIDEArray;
5979       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5980       gameInfo.holdingsSize = 5;
5981       break;
5982     case VariantWildCastle:
5983       pieces = FIDEArray;
5984       /* !!?shuffle with kings guaranteed to be on d or e file */
5985       shuffleOpenings = 1;
5986       break;
5987     case VariantNoCastle:
5988       pieces = FIDEArray;
5989       nrCastlingRights = 0;
5990       /* !!?unconstrained back-rank shuffle */
5991       shuffleOpenings = 1;
5992       break;
5993     }
5994
5995     overrule = 0;
5996     if(appData.NrFiles >= 0) {
5997         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5998         gameInfo.boardWidth = appData.NrFiles;
5999     }
6000     if(appData.NrRanks >= 0) {
6001         gameInfo.boardHeight = appData.NrRanks;
6002     }
6003     if(appData.holdingsSize >= 0) {
6004         i = appData.holdingsSize;
6005         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6006         gameInfo.holdingsSize = i;
6007     }
6008     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6009     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6010         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6011
6012     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6013     if(pawnRow < 1) pawnRow = 1;
6014     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6015
6016     /* User pieceToChar list overrules defaults */
6017     if(appData.pieceToCharTable != NULL)
6018         SetCharTable(pieceToChar, appData.pieceToCharTable);
6019
6020     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6021
6022         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6023             s = (ChessSquare) 0; /* account holding counts in guard band */
6024         for( i=0; i<BOARD_HEIGHT; i++ )
6025             initialPosition[i][j] = s;
6026
6027         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6028         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6029         initialPosition[pawnRow][j] = WhitePawn;
6030         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6031         if(gameInfo.variant == VariantXiangqi) {
6032             if(j&1) {
6033                 initialPosition[pawnRow][j] =
6034                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6035                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6036                    initialPosition[2][j] = WhiteCannon;
6037                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6038                 }
6039             }
6040         }
6041         if(gameInfo.variant == VariantGrand) {
6042             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6043                initialPosition[0][j] = WhiteRook;
6044                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6045             }
6046         }
6047         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6048     }
6049     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6050
6051             j=BOARD_LEFT+1;
6052             initialPosition[1][j] = WhiteBishop;
6053             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6054             j=BOARD_RGHT-2;
6055             initialPosition[1][j] = WhiteRook;
6056             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6057     }
6058
6059     if( nrCastlingRights == -1) {
6060         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6061         /*       This sets default castling rights from none to normal corners   */
6062         /* Variants with other castling rights must set them themselves above    */
6063         nrCastlingRights = 6;
6064
6065         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6066         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6067         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6068         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6069         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6070         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6071      }
6072
6073      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6074      if(gameInfo.variant == VariantGreat) { // promotion commoners
6075         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6076         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6077         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6078         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6079      }
6080      if( gameInfo.variant == VariantSChess ) {
6081       initialPosition[1][0] = BlackMarshall;
6082       initialPosition[2][0] = BlackAngel;
6083       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6084       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6085       initialPosition[1][1] = initialPosition[2][1] = 
6086       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6087      }
6088   if (appData.debugMode) {
6089     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6090   }
6091     if(shuffleOpenings) {
6092         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6093         startedFromSetupPosition = TRUE;
6094     }
6095     if(startedFromPositionFile) {
6096       /* [HGM] loadPos: use PositionFile for every new game */
6097       CopyBoard(initialPosition, filePosition);
6098       for(i=0; i<nrCastlingRights; i++)
6099           initialRights[i] = filePosition[CASTLING][i];
6100       startedFromSetupPosition = TRUE;
6101     }
6102
6103     CopyBoard(boards[0], initialPosition);
6104
6105     if(oldx != gameInfo.boardWidth ||
6106        oldy != gameInfo.boardHeight ||
6107        oldv != gameInfo.variant ||
6108        oldh != gameInfo.holdingsWidth
6109                                          )
6110             InitDrawingSizes(-2 ,0);
6111
6112     oldv = gameInfo.variant;
6113     if (redraw)
6114       DrawPosition(TRUE, boards[currentMove]);
6115 }
6116
6117 void
6118 SendBoard (ChessProgramState *cps, int moveNum)
6119 {
6120     char message[MSG_SIZ];
6121
6122     if (cps->useSetboard) {
6123       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6124       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6125       SendToProgram(message, cps);
6126       free(fen);
6127
6128     } else {
6129       ChessSquare *bp;
6130       int i, j, left=0, right=BOARD_WIDTH;
6131       /* Kludge to set black to move, avoiding the troublesome and now
6132        * deprecated "black" command.
6133        */
6134       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6135         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6136
6137       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6138
6139       SendToProgram("edit\n", cps);
6140       SendToProgram("#\n", cps);
6141       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6142         bp = &boards[moveNum][i][left];
6143         for (j = left; j < right; j++, bp++) {
6144           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6145           if ((int) *bp < (int) BlackPawn) {
6146             if(j == BOARD_RGHT+1)
6147                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6148             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6149             if(message[0] == '+' || message[0] == '~') {
6150               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6151                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6152                         AAA + j, ONE + i);
6153             }
6154             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6155                 message[1] = BOARD_RGHT   - 1 - j + '1';
6156                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6157             }
6158             SendToProgram(message, cps);
6159           }
6160         }
6161       }
6162
6163       SendToProgram("c\n", cps);
6164       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6165         bp = &boards[moveNum][i][left];
6166         for (j = left; j < right; j++, bp++) {
6167           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6168           if (((int) *bp != (int) EmptySquare)
6169               && ((int) *bp >= (int) BlackPawn)) {
6170             if(j == BOARD_LEFT-2)
6171                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6172             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6173                     AAA + j, ONE + i);
6174             if(message[0] == '+' || message[0] == '~') {
6175               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6176                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6177                         AAA + j, ONE + i);
6178             }
6179             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6180                 message[1] = BOARD_RGHT   - 1 - j + '1';
6181                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6182             }
6183             SendToProgram(message, cps);
6184           }
6185         }
6186       }
6187
6188       SendToProgram(".\n", cps);
6189     }
6190     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6191 }
6192
6193 char exclusionHeader[MSG_SIZ];
6194 int exCnt, excludePtr;
6195 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6196 static Exclusion excluTab[200];
6197 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6198
6199 static void
6200 WriteMap (int s)
6201 {
6202     int j;
6203     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6204     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6205 }
6206
6207 static void
6208 ClearMap ()
6209 {
6210     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6211     excludePtr = 24; exCnt = 0;
6212     WriteMap(0);
6213 }
6214
6215 static void
6216 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6217 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6218     char buf[2*MOVE_LEN], *p;
6219     Exclusion *e = excluTab;
6220     int i;
6221     for(i=0; i<exCnt; i++)
6222         if(e[i].ff == fromX && e[i].fr == fromY &&
6223            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6224     if(i == exCnt) { // was not in exclude list; add it
6225         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6226         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6227             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6228             return; // abort
6229         }
6230         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6231         excludePtr++; e[i].mark = excludePtr++;
6232         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6233         exCnt++;
6234     }
6235     exclusionHeader[e[i].mark] = state;
6236 }
6237
6238 static int
6239 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6240 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6241     char buf[MSG_SIZ];
6242     int j, k;
6243     ChessMove moveType;
6244     if((signed char)promoChar == -1) { // kludge to indicate best move
6245         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6246             return 1; // if unparsable, abort
6247     }
6248     // update exclusion map (resolving toggle by consulting existing state)
6249     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6250     j = k%8; k >>= 3;
6251     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6252     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6253          excludeMap[k] |=   1<<j;
6254     else excludeMap[k] &= ~(1<<j);
6255     // update header
6256     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6257     // inform engine
6258     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6259     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6260     SendToBoth(buf);
6261     return (state == '+');
6262 }
6263
6264 static void
6265 ExcludeClick (int index)
6266 {
6267     int i, j;
6268     Exclusion *e = excluTab;
6269     if(index < 25) { // none, best or tail clicked
6270         if(index < 13) { // none: include all
6271             WriteMap(0); // clear map
6272             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6273             SendToBoth("include all\n"); // and inform engine
6274         } else if(index > 18) { // tail
6275             if(exclusionHeader[19] == '-') { // tail was excluded
6276                 SendToBoth("include all\n");
6277                 WriteMap(0); // clear map completely
6278                 // now re-exclude selected moves
6279                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6280                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6281             } else { // tail was included or in mixed state
6282                 SendToBoth("exclude all\n");
6283                 WriteMap(0xFF); // fill map completely
6284                 // now re-include selected moves
6285                 j = 0; // count them
6286                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6287                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6288                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6289             }
6290         } else { // best
6291             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6292         }
6293     } else {
6294         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6295             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6296             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6297             break;
6298         }
6299     }
6300 }
6301
6302 ChessSquare
6303 DefaultPromoChoice (int white)
6304 {
6305     ChessSquare result;
6306     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6307         result = WhiteFerz; // no choice
6308     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6309         result= WhiteKing; // in Suicide Q is the last thing we want
6310     else if(gameInfo.variant == VariantSpartan)
6311         result = white ? WhiteQueen : WhiteAngel;
6312     else result = WhiteQueen;
6313     if(!white) result = WHITE_TO_BLACK result;
6314     return result;
6315 }
6316
6317 static int autoQueen; // [HGM] oneclick
6318
6319 int
6320 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6321 {
6322     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6323     /* [HGM] add Shogi promotions */
6324     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6325     ChessSquare piece;
6326     ChessMove moveType;
6327     Boolean premove;
6328
6329     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6330     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6331
6332     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6333       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6334         return FALSE;
6335
6336     piece = boards[currentMove][fromY][fromX];
6337     if(gameInfo.variant == VariantShogi) {
6338         promotionZoneSize = BOARD_HEIGHT/3;
6339         highestPromotingPiece = (int)WhiteFerz;
6340     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6341         promotionZoneSize = 3;
6342     }
6343
6344     // Treat Lance as Pawn when it is not representing Amazon
6345     if(gameInfo.variant != VariantSuper) {
6346         if(piece == WhiteLance) piece = WhitePawn; else
6347         if(piece == BlackLance) piece = BlackPawn;
6348     }
6349
6350     // next weed out all moves that do not touch the promotion zone at all
6351     if((int)piece >= BlackPawn) {
6352         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6353              return FALSE;
6354         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6355     } else {
6356         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6357            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6358     }
6359
6360     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6361
6362     // weed out mandatory Shogi promotions
6363     if(gameInfo.variant == VariantShogi) {
6364         if(piece >= BlackPawn) {
6365             if(toY == 0 && piece == BlackPawn ||
6366                toY == 0 && piece == BlackQueen ||
6367                toY <= 1 && piece == BlackKnight) {
6368                 *promoChoice = '+';
6369                 return FALSE;
6370             }
6371         } else {
6372             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6373                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6374                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6375                 *promoChoice = '+';
6376                 return FALSE;
6377             }
6378         }
6379     }
6380
6381     // weed out obviously illegal Pawn moves
6382     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6383         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6384         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6385         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6386         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6387         // note we are not allowed to test for valid (non-)capture, due to premove
6388     }
6389
6390     // we either have a choice what to promote to, or (in Shogi) whether to promote
6391     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6392         *promoChoice = PieceToChar(BlackFerz);  // no choice
6393         return FALSE;
6394     }
6395     // no sense asking what we must promote to if it is going to explode...
6396     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6397         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6398         return FALSE;
6399     }
6400     // give caller the default choice even if we will not make it
6401     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6402     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6403     if(        sweepSelect && gameInfo.variant != VariantGreat
6404                            && gameInfo.variant != VariantGrand
6405                            && gameInfo.variant != VariantSuper) return FALSE;
6406     if(autoQueen) return FALSE; // predetermined
6407
6408     // suppress promotion popup on illegal moves that are not premoves
6409     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6410               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6411     if(appData.testLegality && !premove) {
6412         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6413                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6414         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6415             return FALSE;
6416     }
6417
6418     return TRUE;
6419 }
6420
6421 int
6422 InPalace (int row, int column)
6423 {   /* [HGM] for Xiangqi */
6424     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6425          column < (BOARD_WIDTH + 4)/2 &&
6426          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6427     return FALSE;
6428 }
6429
6430 int
6431 PieceForSquare (int x, int y)
6432 {
6433   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6434      return -1;
6435   else
6436      return boards[currentMove][y][x];
6437 }
6438
6439 int
6440 OKToStartUserMove (int x, int y)
6441 {
6442     ChessSquare from_piece;
6443     int white_piece;
6444
6445     if (matchMode) return FALSE;
6446     if (gameMode == EditPosition) return TRUE;
6447
6448     if (x >= 0 && y >= 0)
6449       from_piece = boards[currentMove][y][x];
6450     else
6451       from_piece = EmptySquare;
6452
6453     if (from_piece == EmptySquare) return FALSE;
6454
6455     white_piece = (int)from_piece >= (int)WhitePawn &&
6456       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6457
6458     switch (gameMode) {
6459       case AnalyzeFile:
6460       case TwoMachinesPlay:
6461       case EndOfGame:
6462         return FALSE;
6463
6464       case IcsObserving:
6465       case IcsIdle:
6466         return FALSE;
6467
6468       case MachinePlaysWhite:
6469       case IcsPlayingBlack:
6470         if (appData.zippyPlay) return FALSE;
6471         if (white_piece) {
6472             DisplayMoveError(_("You are playing Black"));
6473             return FALSE;
6474         }
6475         break;
6476
6477       case MachinePlaysBlack:
6478       case IcsPlayingWhite:
6479         if (appData.zippyPlay) return FALSE;
6480         if (!white_piece) {
6481             DisplayMoveError(_("You are playing White"));
6482             return FALSE;
6483         }
6484         break;
6485
6486       case PlayFromGameFile:
6487             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6488       case EditGame:
6489         if (!white_piece && WhiteOnMove(currentMove)) {
6490             DisplayMoveError(_("It is White's turn"));
6491             return FALSE;
6492         }
6493         if (white_piece && !WhiteOnMove(currentMove)) {
6494             DisplayMoveError(_("It is Black's turn"));
6495             return FALSE;
6496         }
6497         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6498             /* Editing correspondence game history */
6499             /* Could disallow this or prompt for confirmation */
6500             cmailOldMove = -1;
6501         }
6502         break;
6503
6504       case BeginningOfGame:
6505         if (appData.icsActive) return FALSE;
6506         if (!appData.noChessProgram) {
6507             if (!white_piece) {
6508                 DisplayMoveError(_("You are playing White"));
6509                 return FALSE;
6510             }
6511         }
6512         break;
6513
6514       case Training:
6515         if (!white_piece && WhiteOnMove(currentMove)) {
6516             DisplayMoveError(_("It is White's turn"));
6517             return FALSE;
6518         }
6519         if (white_piece && !WhiteOnMove(currentMove)) {
6520             DisplayMoveError(_("It is Black's turn"));
6521             return FALSE;
6522         }
6523         break;
6524
6525       default:
6526       case IcsExamining:
6527         break;
6528     }
6529     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6530         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6531         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6532         && gameMode != AnalyzeFile && gameMode != Training) {
6533         DisplayMoveError(_("Displayed position is not current"));
6534         return FALSE;
6535     }
6536     return TRUE;
6537 }
6538
6539 Boolean
6540 OnlyMove (int *x, int *y, Boolean captures) 
6541 {
6542     DisambiguateClosure cl;
6543     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6544     switch(gameMode) {
6545       case MachinePlaysBlack:
6546       case IcsPlayingWhite:
6547       case BeginningOfGame:
6548         if(!WhiteOnMove(currentMove)) return FALSE;
6549         break;
6550       case MachinePlaysWhite:
6551       case IcsPlayingBlack:
6552         if(WhiteOnMove(currentMove)) return FALSE;
6553         break;
6554       case EditGame:
6555         break;
6556       default:
6557         return FALSE;
6558     }
6559     cl.pieceIn = EmptySquare;
6560     cl.rfIn = *y;
6561     cl.ffIn = *x;
6562     cl.rtIn = -1;
6563     cl.ftIn = -1;
6564     cl.promoCharIn = NULLCHAR;
6565     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6566     if( cl.kind == NormalMove ||
6567         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6568         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6569         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6570       fromX = cl.ff;
6571       fromY = cl.rf;
6572       *x = cl.ft;
6573       *y = cl.rt;
6574       return TRUE;
6575     }
6576     if(cl.kind != ImpossibleMove) return FALSE;
6577     cl.pieceIn = EmptySquare;
6578     cl.rfIn = -1;
6579     cl.ffIn = -1;
6580     cl.rtIn = *y;
6581     cl.ftIn = *x;
6582     cl.promoCharIn = NULLCHAR;
6583     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6584     if( cl.kind == NormalMove ||
6585         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6586         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6587         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6588       fromX = cl.ff;
6589       fromY = cl.rf;
6590       *x = cl.ft;
6591       *y = cl.rt;
6592       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6593       return TRUE;
6594     }
6595     return FALSE;
6596 }
6597
6598 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6599 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6600 int lastLoadGameUseList = FALSE;
6601 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6602 ChessMove lastLoadGameStart = EndOfFile;
6603 int doubleClick;
6604
6605 void
6606 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6607 {
6608     ChessMove moveType;
6609     ChessSquare pup;
6610     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6611
6612     /* Check if the user is playing in turn.  This is complicated because we
6613        let the user "pick up" a piece before it is his turn.  So the piece he
6614        tried to pick up may have been captured by the time he puts it down!
6615        Therefore we use the color the user is supposed to be playing in this
6616        test, not the color of the piece that is currently on the starting
6617        square---except in EditGame mode, where the user is playing both
6618        sides; fortunately there the capture race can't happen.  (It can
6619        now happen in IcsExamining mode, but that's just too bad.  The user
6620        will get a somewhat confusing message in that case.)
6621        */
6622
6623     switch (gameMode) {
6624       case AnalyzeFile:
6625       case TwoMachinesPlay:
6626       case EndOfGame:
6627       case IcsObserving:
6628       case IcsIdle:
6629         /* We switched into a game mode where moves are not accepted,
6630            perhaps while the mouse button was down. */
6631         return;
6632
6633       case MachinePlaysWhite:
6634         /* User is moving for Black */
6635         if (WhiteOnMove(currentMove)) {
6636             DisplayMoveError(_("It is White's turn"));
6637             return;
6638         }
6639         break;
6640
6641       case MachinePlaysBlack:
6642         /* User is moving for White */
6643         if (!WhiteOnMove(currentMove)) {
6644             DisplayMoveError(_("It is Black's turn"));
6645             return;
6646         }
6647         break;
6648
6649       case PlayFromGameFile:
6650             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6651       case EditGame:
6652       case IcsExamining:
6653       case BeginningOfGame:
6654       case AnalyzeMode:
6655       case Training:
6656         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6657         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6658             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6659             /* User is moving for Black */
6660             if (WhiteOnMove(currentMove)) {
6661                 DisplayMoveError(_("It is White's turn"));
6662                 return;
6663             }
6664         } else {
6665             /* User is moving for White */
6666             if (!WhiteOnMove(currentMove)) {
6667                 DisplayMoveError(_("It is Black's turn"));
6668                 return;
6669             }
6670         }
6671         break;
6672
6673       case IcsPlayingBlack:
6674         /* User is moving for Black */
6675         if (WhiteOnMove(currentMove)) {
6676             if (!appData.premove) {
6677                 DisplayMoveError(_("It is White's turn"));
6678             } else if (toX >= 0 && toY >= 0) {
6679                 premoveToX = toX;
6680                 premoveToY = toY;
6681                 premoveFromX = fromX;
6682                 premoveFromY = fromY;
6683                 premovePromoChar = promoChar;
6684                 gotPremove = 1;
6685                 if (appData.debugMode)
6686                     fprintf(debugFP, "Got premove: fromX %d,"
6687                             "fromY %d, toX %d, toY %d\n",
6688                             fromX, fromY, toX, toY);
6689             }
6690             return;
6691         }
6692         break;
6693
6694       case IcsPlayingWhite:
6695         /* User is moving for White */
6696         if (!WhiteOnMove(currentMove)) {
6697             if (!appData.premove) {
6698                 DisplayMoveError(_("It is Black's turn"));
6699             } else if (toX >= 0 && toY >= 0) {
6700                 premoveToX = toX;
6701                 premoveToY = toY;
6702                 premoveFromX = fromX;
6703                 premoveFromY = fromY;
6704                 premovePromoChar = promoChar;
6705                 gotPremove = 1;
6706                 if (appData.debugMode)
6707                     fprintf(debugFP, "Got premove: fromX %d,"
6708                             "fromY %d, toX %d, toY %d\n",
6709                             fromX, fromY, toX, toY);
6710             }
6711             return;
6712         }
6713         break;
6714
6715       default:
6716         break;
6717
6718       case EditPosition:
6719         /* EditPosition, empty square, or different color piece;
6720            click-click move is possible */
6721         if (toX == -2 || toY == -2) {
6722             boards[0][fromY][fromX] = EmptySquare;
6723             DrawPosition(FALSE, boards[currentMove]);
6724             return;
6725         } else if (toX >= 0 && toY >= 0) {
6726             boards[0][toY][toX] = boards[0][fromY][fromX];
6727             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6728                 if(boards[0][fromY][0] != EmptySquare) {
6729                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6730                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6731                 }
6732             } else
6733             if(fromX == BOARD_RGHT+1) {
6734                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6735                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6736                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6737                 }
6738             } else
6739             boards[0][fromY][fromX] = gatingPiece;
6740             DrawPosition(FALSE, boards[currentMove]);
6741             return;
6742         }
6743         return;
6744     }
6745
6746     if(toX < 0 || toY < 0) return;
6747     pup = boards[currentMove][toY][toX];
6748
6749     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6750     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6751          if( pup != EmptySquare ) return;
6752          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6753            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6754                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6755            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6756            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6757            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6758            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6759          fromY = DROP_RANK;
6760     }
6761
6762     /* [HGM] always test for legality, to get promotion info */
6763     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6764                                          fromY, fromX, toY, toX, promoChar);
6765
6766     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6767
6768     /* [HGM] but possibly ignore an IllegalMove result */
6769     if (appData.testLegality) {
6770         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6771             DisplayMoveError(_("Illegal move"));
6772             return;
6773         }
6774     }
6775
6776     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6777         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6778              ClearPremoveHighlights(); // was included
6779         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6780         return;
6781     }
6782
6783     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6784 }
6785
6786 /* Common tail of UserMoveEvent and DropMenuEvent */
6787 int
6788 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6789 {
6790     char *bookHit = 0;
6791
6792     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6793         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6794         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6795         if(WhiteOnMove(currentMove)) {
6796             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6797         } else {
6798             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6799         }
6800     }
6801
6802     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6803        move type in caller when we know the move is a legal promotion */
6804     if(moveType == NormalMove && promoChar)
6805         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6806
6807     /* [HGM] <popupFix> The following if has been moved here from
6808        UserMoveEvent(). Because it seemed to belong here (why not allow
6809        piece drops in training games?), and because it can only be
6810        performed after it is known to what we promote. */
6811     if (gameMode == Training) {
6812       /* compare the move played on the board to the next move in the
6813        * game. If they match, display the move and the opponent's response.
6814        * If they don't match, display an error message.
6815        */
6816       int saveAnimate;
6817       Board testBoard;
6818       CopyBoard(testBoard, boards[currentMove]);
6819       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6820
6821       if (CompareBoards(testBoard, boards[currentMove+1])) {
6822         ForwardInner(currentMove+1);
6823
6824         /* Autoplay the opponent's response.
6825          * if appData.animate was TRUE when Training mode was entered,
6826          * the response will be animated.
6827          */
6828         saveAnimate = appData.animate;
6829         appData.animate = animateTraining;
6830         ForwardInner(currentMove+1);
6831         appData.animate = saveAnimate;
6832
6833         /* check for the end of the game */
6834         if (currentMove >= forwardMostMove) {
6835           gameMode = PlayFromGameFile;
6836           ModeHighlight();
6837           SetTrainingModeOff();
6838           DisplayInformation(_("End of game"));
6839         }
6840       } else {
6841         DisplayError(_("Incorrect move"), 0);
6842       }
6843       return 1;
6844     }
6845
6846   /* Ok, now we know that the move is good, so we can kill
6847      the previous line in Analysis Mode */
6848   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6849                                 && currentMove < forwardMostMove) {
6850     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6851     else forwardMostMove = currentMove;
6852   }
6853
6854   ClearMap();
6855
6856   /* If we need the chess program but it's dead, restart it */
6857   ResurrectChessProgram();
6858
6859   /* A user move restarts a paused game*/
6860   if (pausing)
6861     PauseEvent();
6862
6863   thinkOutput[0] = NULLCHAR;
6864
6865   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6866
6867   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6868     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6869     return 1;
6870   }
6871
6872   if (gameMode == BeginningOfGame) {
6873     if (appData.noChessProgram) {
6874       gameMode = EditGame;
6875       SetGameInfo();
6876     } else {
6877       char buf[MSG_SIZ];
6878       gameMode = MachinePlaysBlack;
6879       StartClocks();
6880       SetGameInfo();
6881       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6882       DisplayTitle(buf);
6883       if (first.sendName) {
6884         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6885         SendToProgram(buf, &first);
6886       }
6887       StartClocks();
6888     }
6889     ModeHighlight();
6890   }
6891
6892   /* Relay move to ICS or chess engine */
6893   if (appData.icsActive) {
6894     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6895         gameMode == IcsExamining) {
6896       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6897         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6898         SendToICS("draw ");
6899         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6900       }
6901       // also send plain move, in case ICS does not understand atomic claims
6902       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6903       ics_user_moved = 1;
6904     }
6905   } else {
6906     if (first.sendTime && (gameMode == BeginningOfGame ||
6907                            gameMode == MachinePlaysWhite ||
6908                            gameMode == MachinePlaysBlack)) {
6909       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6910     }
6911     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6912          // [HGM] book: if program might be playing, let it use book
6913         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6914         first.maybeThinking = TRUE;
6915     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6916         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6917         SendBoard(&first, currentMove+1);
6918         if(second.analyzing) {
6919             if(!second.useSetboard) SendToProgram("undo\n", &second);
6920             SendBoard(&second, currentMove+1);
6921         }
6922     } else {
6923         SendMoveToProgram(forwardMostMove-1, &first);
6924         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6925     }
6926     if (currentMove == cmailOldMove + 1) {
6927       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6928     }
6929   }
6930
6931   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6932
6933   switch (gameMode) {
6934   case EditGame:
6935     if(appData.testLegality)
6936     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6937     case MT_NONE:
6938     case MT_CHECK:
6939       break;
6940     case MT_CHECKMATE:
6941     case MT_STAINMATE:
6942       if (WhiteOnMove(currentMove)) {
6943         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6944       } else {
6945         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6946       }
6947       break;
6948     case MT_STALEMATE:
6949       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6950       break;
6951     }
6952     break;
6953
6954   case MachinePlaysBlack:
6955   case MachinePlaysWhite:
6956     /* disable certain menu options while machine is thinking */
6957     SetMachineThinkingEnables();
6958     break;
6959
6960   default:
6961     break;
6962   }
6963
6964   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6965   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6966
6967   if(bookHit) { // [HGM] book: simulate book reply
6968         static char bookMove[MSG_SIZ]; // a bit generous?
6969
6970         programStats.nodes = programStats.depth = programStats.time =
6971         programStats.score = programStats.got_only_move = 0;
6972         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6973
6974         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6975         strcat(bookMove, bookHit);
6976         HandleMachineMove(bookMove, &first);
6977   }
6978   return 1;
6979 }
6980
6981 void
6982 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6983 {
6984     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6985     Markers *m = (Markers *) closure;
6986     if(rf == fromY && ff == fromX)
6987         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6988                          || kind == WhiteCapturesEnPassant
6989                          || kind == BlackCapturesEnPassant);
6990     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6991 }
6992
6993 void
6994 MarkTargetSquares (int clear)
6995 {
6996   int x, y;
6997   if(clear) // no reason to ever suppress clearing
6998     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6999   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7000      !appData.testLegality || gameMode == EditPosition) return;
7001   if(!clear) {
7002     int capt = 0;
7003     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7004     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7005       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7006       if(capt)
7007       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7008     }
7009   }
7010   DrawPosition(FALSE, NULL);
7011 }
7012
7013 int
7014 Explode (Board board, int fromX, int fromY, int toX, int toY)
7015 {
7016     if(gameInfo.variant == VariantAtomic &&
7017        (board[toY][toX] != EmptySquare ||                     // capture?
7018         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7019                          board[fromY][fromX] == BlackPawn   )
7020       )) {
7021         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7022         return TRUE;
7023     }
7024     return FALSE;
7025 }
7026
7027 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7028
7029 int
7030 CanPromote (ChessSquare piece, int y)
7031 {
7032         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7033         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7034         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7035            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7036            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7037                                                   gameInfo.variant == VariantMakruk) return FALSE;
7038         return (piece == BlackPawn && y == 1 ||
7039                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7040                 piece == BlackLance && y == 1 ||
7041                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7042 }
7043
7044 void
7045 LeftClick (ClickType clickType, int xPix, int yPix)
7046 {
7047     int x, y;
7048     Boolean saveAnimate;
7049     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7050     char promoChoice = NULLCHAR;
7051     ChessSquare piece;
7052     static TimeMark lastClickTime, prevClickTime;
7053
7054     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7055
7056     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7057
7058     if (clickType == Press) ErrorPopDown();
7059
7060     x = EventToSquare(xPix, BOARD_WIDTH);
7061     y = EventToSquare(yPix, BOARD_HEIGHT);
7062     if (!flipView && y >= 0) {
7063         y = BOARD_HEIGHT - 1 - y;
7064     }
7065     if (flipView && x >= 0) {
7066         x = BOARD_WIDTH - 1 - x;
7067     }
7068
7069     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7070         defaultPromoChoice = promoSweep;
7071         promoSweep = EmptySquare;   // terminate sweep
7072         promoDefaultAltered = TRUE;
7073         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7074     }
7075
7076     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7077         if(clickType == Release) return; // ignore upclick of click-click destination
7078         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7079         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7080         if(gameInfo.holdingsWidth &&
7081                 (WhiteOnMove(currentMove)
7082                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7083                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7084             // click in right holdings, for determining promotion piece
7085             ChessSquare p = boards[currentMove][y][x];
7086             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7087             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7088             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7089                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7090                 fromX = fromY = -1;
7091                 return;
7092             }
7093         }
7094         DrawPosition(FALSE, boards[currentMove]);
7095         return;
7096     }
7097
7098     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7099     if(clickType == Press
7100             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7101               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7102               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7103         return;
7104
7105     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7106         // could be static click on premove from-square: abort premove
7107         gotPremove = 0;
7108         ClearPremoveHighlights();
7109     }
7110
7111     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7112         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7113
7114     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7115         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7116                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7117         defaultPromoChoice = DefaultPromoChoice(side);
7118     }
7119
7120     autoQueen = appData.alwaysPromoteToQueen;
7121
7122     if (fromX == -1) {
7123       int originalY = y;
7124       gatingPiece = EmptySquare;
7125       if (clickType != Press) {
7126         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7127             DragPieceEnd(xPix, yPix); dragging = 0;
7128             DrawPosition(FALSE, NULL);
7129         }
7130         return;
7131       }
7132       doubleClick = FALSE;
7133       if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
7134         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7135       }
7136       fromX = x; fromY = y; toX = toY = -1;
7137       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7138          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7139          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7140             /* First square */
7141             if (OKToStartUserMove(fromX, fromY)) {
7142                 second = 0;
7143                 MarkTargetSquares(0);
7144                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7145                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7146                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7147                     promoSweep = defaultPromoChoice;
7148                     selectFlag = 0; lastX = xPix; lastY = yPix;
7149                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7150                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7151                 }
7152                 if (appData.highlightDragging) {
7153                     SetHighlights(fromX, fromY, -1, -1);
7154                 } else {
7155                     ClearHighlights();
7156                 }
7157             } else fromX = fromY = -1;
7158             return;
7159         }
7160     }
7161
7162     /* fromX != -1 */
7163     if (clickType == Press && gameMode != EditPosition) {
7164         ChessSquare fromP;
7165         ChessSquare toP;
7166         int frc;
7167
7168         // ignore off-board to clicks
7169         if(y < 0 || x < 0) return;
7170
7171         /* Check if clicking again on the same color piece */
7172         fromP = boards[currentMove][fromY][fromX];
7173         toP = boards[currentMove][y][x];
7174         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7175         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7176              WhitePawn <= toP && toP <= WhiteKing &&
7177              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7178              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7179             (BlackPawn <= fromP && fromP <= BlackKing &&
7180              BlackPawn <= toP && toP <= BlackKing &&
7181              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7182              !(fromP == BlackKing && toP == BlackRook && frc))) {
7183             /* Clicked again on same color piece -- changed his mind */
7184             second = (x == fromX && y == fromY);
7185             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7186                 second = FALSE; // first double-click rather than scond click
7187                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7188             }
7189             promoDefaultAltered = FALSE;
7190             MarkTargetSquares(1);
7191            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7192             if (appData.highlightDragging) {
7193                 SetHighlights(x, y, -1, -1);
7194             } else {
7195                 ClearHighlights();
7196             }
7197             if (OKToStartUserMove(x, y)) {
7198                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7199                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7200                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7201                  gatingPiece = boards[currentMove][fromY][fromX];
7202                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7203                 fromX = x;
7204                 fromY = y; dragging = 1;
7205                 MarkTargetSquares(0);
7206                 DragPieceBegin(xPix, yPix, FALSE);
7207                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7208                     promoSweep = defaultPromoChoice;
7209                     selectFlag = 0; lastX = xPix; lastY = yPix;
7210                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7211                 }
7212             }
7213            }
7214            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7215            second = FALSE; 
7216         }
7217         // ignore clicks on holdings
7218         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7219     }
7220
7221     if (clickType == Release && x == fromX && y == fromY) {
7222         DragPieceEnd(xPix, yPix); dragging = 0;
7223         if(clearFlag) {
7224             // a deferred attempt to click-click move an empty square on top of a piece
7225             boards[currentMove][y][x] = EmptySquare;
7226             ClearHighlights();
7227             DrawPosition(FALSE, boards[currentMove]);
7228             fromX = fromY = -1; clearFlag = 0;
7229             return;
7230         }
7231         if (appData.animateDragging) {
7232             /* Undo animation damage if any */
7233             DrawPosition(FALSE, NULL);
7234         }
7235         if (second || sweepSelecting) {
7236             /* Second up/down in same square; just abort move */
7237             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7238             second = sweepSelecting = 0;
7239             fromX = fromY = -1;
7240             gatingPiece = EmptySquare;
7241             ClearHighlights();
7242             gotPremove = 0;
7243             ClearPremoveHighlights();
7244         } else {
7245             /* First upclick in same square; start click-click mode */
7246             SetHighlights(x, y, -1, -1);
7247         }
7248         return;
7249     }
7250
7251     clearFlag = 0;
7252
7253     /* we now have a different from- and (possibly off-board) to-square */
7254     /* Completed move */
7255     if(!sweepSelecting) {
7256         toX = x;
7257         toY = y;
7258     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7259
7260     saveAnimate = appData.animate;
7261     if (clickType == Press) {
7262         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7263             // must be Edit Position mode with empty-square selected
7264             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7265             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7266             return;
7267         }
7268         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7269           if(appData.sweepSelect) {
7270             ChessSquare piece = boards[currentMove][fromY][fromX];
7271             promoSweep = defaultPromoChoice;
7272             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7273             selectFlag = 0; lastX = xPix; lastY = yPix;
7274             Sweep(0); // Pawn that is going to promote: preview promotion piece
7275             sweepSelecting = 1;
7276             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7277             MarkTargetSquares(1);
7278           }
7279           return; // promo popup appears on up-click
7280         }
7281         /* Finish clickclick move */
7282         if (appData.animate || appData.highlightLastMove) {
7283             SetHighlights(fromX, fromY, toX, toY);
7284         } else {
7285             ClearHighlights();
7286         }
7287     } else {
7288 #if 0
7289 // [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
7290         /* Finish drag move */
7291         if (appData.highlightLastMove) {
7292             SetHighlights(fromX, fromY, toX, toY);
7293         } else {
7294             ClearHighlights();
7295         }
7296 #endif
7297         DragPieceEnd(xPix, yPix); dragging = 0;
7298         /* Don't animate move and drag both */
7299         appData.animate = FALSE;
7300     }
7301
7302     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7303     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7304         ChessSquare piece = boards[currentMove][fromY][fromX];
7305         if(gameMode == EditPosition && piece != EmptySquare &&
7306            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7307             int n;
7308
7309             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7310                 n = PieceToNumber(piece - (int)BlackPawn);
7311                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7312                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7313                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7314             } else
7315             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7316                 n = PieceToNumber(piece);
7317                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7318                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7319                 boards[currentMove][n][BOARD_WIDTH-2]++;
7320             }
7321             boards[currentMove][fromY][fromX] = EmptySquare;
7322         }
7323         ClearHighlights();
7324         fromX = fromY = -1;
7325         MarkTargetSquares(1);
7326         DrawPosition(TRUE, boards[currentMove]);
7327         return;
7328     }
7329
7330     // off-board moves should not be highlighted
7331     if(x < 0 || y < 0) ClearHighlights();
7332
7333     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7334
7335     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7336         SetHighlights(fromX, fromY, toX, toY);
7337         MarkTargetSquares(1);
7338         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7339             // [HGM] super: promotion to captured piece selected from holdings
7340             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7341             promotionChoice = TRUE;
7342             // kludge follows to temporarily execute move on display, without promoting yet
7343             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7344             boards[currentMove][toY][toX] = p;
7345             DrawPosition(FALSE, boards[currentMove]);
7346             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7347             boards[currentMove][toY][toX] = q;
7348             DisplayMessage("Click in holdings to choose piece", "");
7349             return;
7350         }
7351         PromotionPopUp();
7352     } else {
7353         int oldMove = currentMove;
7354         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7355         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7356         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7357         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7358            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7359             DrawPosition(TRUE, boards[currentMove]);
7360         MarkTargetSquares(1);
7361         fromX = fromY = -1;
7362     }
7363     appData.animate = saveAnimate;
7364     if (appData.animate || appData.animateDragging) {
7365         /* Undo animation damage if needed */
7366         DrawPosition(FALSE, NULL);
7367     }
7368 }
7369
7370 int
7371 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7372 {   // front-end-free part taken out of PieceMenuPopup
7373     int whichMenu; int xSqr, ySqr;
7374
7375     if(seekGraphUp) { // [HGM] seekgraph
7376         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7377         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7378         return -2;
7379     }
7380
7381     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7382          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7383         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7384         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7385         if(action == Press)   {
7386             originalFlip = flipView;
7387             flipView = !flipView; // temporarily flip board to see game from partners perspective
7388             DrawPosition(TRUE, partnerBoard);
7389             DisplayMessage(partnerStatus, "");
7390             partnerUp = TRUE;
7391         } else if(action == Release) {
7392             flipView = originalFlip;
7393             DrawPosition(TRUE, boards[currentMove]);
7394             partnerUp = FALSE;
7395         }
7396         return -2;
7397     }
7398
7399     xSqr = EventToSquare(x, BOARD_WIDTH);
7400     ySqr = EventToSquare(y, BOARD_HEIGHT);
7401     if (action == Release) {
7402         if(pieceSweep != EmptySquare) {
7403             EditPositionMenuEvent(pieceSweep, toX, toY);
7404             pieceSweep = EmptySquare;
7405         } else UnLoadPV(); // [HGM] pv
7406     }
7407     if (action != Press) return -2; // return code to be ignored
7408     switch (gameMode) {
7409       case IcsExamining:
7410         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7411       case EditPosition:
7412         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7413         if (xSqr < 0 || ySqr < 0) return -1;
7414         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7415         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7416         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7417         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7418         NextPiece(0);
7419         return 2; // grab
7420       case IcsObserving:
7421         if(!appData.icsEngineAnalyze) return -1;
7422       case IcsPlayingWhite:
7423       case IcsPlayingBlack:
7424         if(!appData.zippyPlay) goto noZip;
7425       case AnalyzeMode:
7426       case AnalyzeFile:
7427       case MachinePlaysWhite:
7428       case MachinePlaysBlack:
7429       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7430         if (!appData.dropMenu) {
7431           LoadPV(x, y);
7432           return 2; // flag front-end to grab mouse events
7433         }
7434         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7435            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7436       case EditGame:
7437       noZip:
7438         if (xSqr < 0 || ySqr < 0) return -1;
7439         if (!appData.dropMenu || appData.testLegality &&
7440             gameInfo.variant != VariantBughouse &&
7441             gameInfo.variant != VariantCrazyhouse) return -1;
7442         whichMenu = 1; // drop menu
7443         break;
7444       default:
7445         return -1;
7446     }
7447
7448     if (((*fromX = xSqr) < 0) ||
7449         ((*fromY = ySqr) < 0)) {
7450         *fromX = *fromY = -1;
7451         return -1;
7452     }
7453     if (flipView)
7454       *fromX = BOARD_WIDTH - 1 - *fromX;
7455     else
7456       *fromY = BOARD_HEIGHT - 1 - *fromY;
7457
7458     return whichMenu;
7459 }
7460
7461 void
7462 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7463 {
7464 //    char * hint = lastHint;
7465     FrontEndProgramStats stats;
7466
7467     stats.which = cps == &first ? 0 : 1;
7468     stats.depth = cpstats->depth;
7469     stats.nodes = cpstats->nodes;
7470     stats.score = cpstats->score;
7471     stats.time = cpstats->time;
7472     stats.pv = cpstats->movelist;
7473     stats.hint = lastHint;
7474     stats.an_move_index = 0;
7475     stats.an_move_count = 0;
7476
7477     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7478         stats.hint = cpstats->move_name;
7479         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7480         stats.an_move_count = cpstats->nr_moves;
7481     }
7482
7483     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
7484
7485     SetProgramStats( &stats );
7486 }
7487
7488 void
7489 ClearEngineOutputPane (int which)
7490 {
7491     static FrontEndProgramStats dummyStats;
7492     dummyStats.which = which;
7493     dummyStats.pv = "#";
7494     SetProgramStats( &dummyStats );
7495 }
7496
7497 #define MAXPLAYERS 500
7498
7499 char *
7500 TourneyStandings (int display)
7501 {
7502     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7503     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7504     char result, *p, *names[MAXPLAYERS];
7505
7506     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7507         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7508     names[0] = p = strdup(appData.participants);
7509     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7510
7511     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7512
7513     while(result = appData.results[nr]) {
7514         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7515         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7516         wScore = bScore = 0;
7517         switch(result) {
7518           case '+': wScore = 2; break;
7519           case '-': bScore = 2; break;
7520           case '=': wScore = bScore = 1; break;
7521           case ' ':
7522           case '*': return strdup("busy"); // tourney not finished
7523         }
7524         score[w] += wScore;
7525         score[b] += bScore;
7526         games[w]++;
7527         games[b]++;
7528         nr++;
7529     }
7530     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7531     for(w=0; w<nPlayers; w++) {
7532         bScore = -1;
7533         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7534         ranking[w] = b; points[w] = bScore; score[b] = -2;
7535     }
7536     p = malloc(nPlayers*34+1);
7537     for(w=0; w<nPlayers && w<display; w++)
7538         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7539     free(names[0]);
7540     return p;
7541 }
7542
7543 void
7544 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7545 {       // count all piece types
7546         int p, f, r;
7547         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7548         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7549         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7550                 p = board[r][f];
7551                 pCnt[p]++;
7552                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7553                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7554                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7555                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7556                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7557                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7558         }
7559 }
7560
7561 int
7562 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7563 {
7564         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7565         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7566
7567         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7568         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7569         if(myPawns == 2 && nMine == 3) // KPP
7570             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7571         if(myPawns == 1 && nMine == 2) // KP
7572             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7573         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7574             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7575         if(myPawns) return FALSE;
7576         if(pCnt[WhiteRook+side])
7577             return pCnt[BlackRook-side] ||
7578                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7579                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7580                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7581         if(pCnt[WhiteCannon+side]) {
7582             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7583             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7584         }
7585         if(pCnt[WhiteKnight+side])
7586             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7587         return FALSE;
7588 }
7589
7590 int
7591 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7592 {
7593         VariantClass v = gameInfo.variant;
7594
7595         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7596         if(v == VariantShatranj) return TRUE; // always winnable through baring
7597         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7598         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7599
7600         if(v == VariantXiangqi) {
7601                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7602
7603                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7604                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7605                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7606                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7607                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7608                 if(stale) // we have at least one last-rank P plus perhaps C
7609                     return majors // KPKX
7610                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7611                 else // KCA*E*
7612                     return pCnt[WhiteFerz+side] // KCAK
7613                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7614                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7615                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7616
7617         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7618                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7619
7620                 if(nMine == 1) return FALSE; // bare King
7621                 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
7622                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7623                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7624                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7625                 if(pCnt[WhiteKnight+side])
7626                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7627                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7628                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7629                 if(nBishops)
7630                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7631                 if(pCnt[WhiteAlfil+side])
7632                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7633                 if(pCnt[WhiteWazir+side])
7634                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7635         }
7636
7637         return TRUE;
7638 }
7639
7640 int
7641 CompareWithRights (Board b1, Board b2)
7642 {
7643     int rights = 0;
7644     if(!CompareBoards(b1, b2)) return FALSE;
7645     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7646     /* compare castling rights */
7647     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7648            rights++; /* King lost rights, while rook still had them */
7649     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7650         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7651            rights++; /* but at least one rook lost them */
7652     }
7653     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7654            rights++;
7655     if( b1[CASTLING][5] != NoRights ) {
7656         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7657            rights++;
7658     }
7659     return rights == 0;
7660 }
7661
7662 int
7663 Adjudicate (ChessProgramState *cps)
7664 {       // [HGM] some adjudications useful with buggy engines
7665         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7666         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7667         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7668         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7669         int k, count = 0; static int bare = 1;
7670         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7671         Boolean canAdjudicate = !appData.icsActive;
7672
7673         // most tests only when we understand the game, i.e. legality-checking on
7674             if( appData.testLegality )
7675             {   /* [HGM] Some more adjudications for obstinate engines */
7676                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7677                 static int moveCount = 6;
7678                 ChessMove result;
7679                 char *reason = NULL;
7680
7681                 /* Count what is on board. */
7682                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7683
7684                 /* Some material-based adjudications that have to be made before stalemate test */
7685                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7686                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7687                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7688                      if(canAdjudicate && appData.checkMates) {
7689                          if(engineOpponent)
7690                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7691                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7692                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7693                          return 1;
7694                      }
7695                 }
7696
7697                 /* Bare King in Shatranj (loses) or Losers (wins) */
7698                 if( nrW == 1 || nrB == 1) {
7699                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7700                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7701                      if(canAdjudicate && appData.checkMates) {
7702                          if(engineOpponent)
7703                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7704                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7705                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7706                          return 1;
7707                      }
7708                   } else
7709                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7710                   {    /* bare King */
7711                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7712                         if(canAdjudicate && appData.checkMates) {
7713                             /* but only adjudicate if adjudication enabled */
7714                             if(engineOpponent)
7715                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7716                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7717                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7718                             return 1;
7719                         }
7720                   }
7721                 } else bare = 1;
7722
7723
7724             // don't wait for engine to announce game end if we can judge ourselves
7725             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7726               case MT_CHECK:
7727                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7728                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7729                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7730                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7731                             checkCnt++;
7732                         if(checkCnt >= 2) {
7733                             reason = "Xboard adjudication: 3rd check";
7734                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7735                             break;
7736                         }
7737                     }
7738                 }
7739               case MT_NONE:
7740               default:
7741                 break;
7742               case MT_STALEMATE:
7743               case MT_STAINMATE:
7744                 reason = "Xboard adjudication: Stalemate";
7745                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7746                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7747                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7748                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7749                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7750                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7751                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7752                                                                         EP_CHECKMATE : EP_WINS);
7753                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7754                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7755                 }
7756                 break;
7757               case MT_CHECKMATE:
7758                 reason = "Xboard adjudication: Checkmate";
7759                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7760                 break;
7761             }
7762
7763                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7764                     case EP_STALEMATE:
7765                         result = GameIsDrawn; break;
7766                     case EP_CHECKMATE:
7767                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7768                     case EP_WINS:
7769                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7770                     default:
7771                         result = EndOfFile;
7772                 }
7773                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7774                     if(engineOpponent)
7775                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7776                     GameEnds( result, reason, GE_XBOARD );
7777                     return 1;
7778                 }
7779
7780                 /* Next absolutely insufficient mating material. */
7781                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7782                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7783                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7784
7785                      /* always flag draws, for judging claims */
7786                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7787
7788                      if(canAdjudicate && appData.materialDraws) {
7789                          /* but only adjudicate them if adjudication enabled */
7790                          if(engineOpponent) {
7791                            SendToProgram("force\n", engineOpponent); // suppress reply
7792                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7793                          }
7794                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7795                          return 1;
7796                      }
7797                 }
7798
7799                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7800                 if(gameInfo.variant == VariantXiangqi ?
7801                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7802                  : nrW + nrB == 4 &&
7803                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7804                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7805                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7806                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7807                    ) ) {
7808                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7809                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7810                           if(engineOpponent) {
7811                             SendToProgram("force\n", engineOpponent); // suppress reply
7812                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7813                           }
7814                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7815                           return 1;
7816                      }
7817                 } else moveCount = 6;
7818             }
7819
7820         // Repetition draws and 50-move rule can be applied independently of legality testing
7821
7822                 /* Check for rep-draws */
7823                 count = 0;
7824                 for(k = forwardMostMove-2;
7825                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7826                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7827                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7828                     k-=2)
7829                 {   int rights=0;
7830                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7831                         /* compare castling rights */
7832                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7833                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7834                                 rights++; /* King lost rights, while rook still had them */
7835                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7836                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7837                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7838                                    rights++; /* but at least one rook lost them */
7839                         }
7840                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7841                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7842                                 rights++;
7843                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7844                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7845                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7846                                    rights++;
7847                         }
7848                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7849                             && appData.drawRepeats > 1) {
7850                              /* adjudicate after user-specified nr of repeats */
7851                              int result = GameIsDrawn;
7852                              char *details = "XBoard adjudication: repetition draw";
7853                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7854                                 // [HGM] xiangqi: check for forbidden perpetuals
7855                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7856                                 for(m=forwardMostMove; m>k; m-=2) {
7857                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7858                                         ourPerpetual = 0; // the current mover did not always check
7859                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7860                                         hisPerpetual = 0; // the opponent did not always check
7861                                 }
7862                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7863                                                                         ourPerpetual, hisPerpetual);
7864                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7865                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7866                                     details = "Xboard adjudication: perpetual checking";
7867                                 } else
7868                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7869                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7870                                 } else
7871                                 // Now check for perpetual chases
7872                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7873                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7874                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7875                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7876                                         static char resdet[MSG_SIZ];
7877                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7878                                         details = resdet;
7879                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7880                                     } else
7881                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7882                                         break; // Abort repetition-checking loop.
7883                                 }
7884                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7885                              }
7886                              if(engineOpponent) {
7887                                SendToProgram("force\n", engineOpponent); // suppress reply
7888                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7889                              }
7890                              GameEnds( result, details, GE_XBOARD );
7891                              return 1;
7892                         }
7893                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7894                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7895                     }
7896                 }
7897
7898                 /* Now we test for 50-move draws. Determine ply count */
7899                 count = forwardMostMove;
7900                 /* look for last irreversble move */
7901                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7902                     count--;
7903                 /* if we hit starting position, add initial plies */
7904                 if( count == backwardMostMove )
7905                     count -= initialRulePlies;
7906                 count = forwardMostMove - count;
7907                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7908                         // adjust reversible move counter for checks in Xiangqi
7909                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7910                         if(i < backwardMostMove) i = backwardMostMove;
7911                         while(i <= forwardMostMove) {
7912                                 lastCheck = inCheck; // check evasion does not count
7913                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7914                                 if(inCheck || lastCheck) count--; // check does not count
7915                                 i++;
7916                         }
7917                 }
7918                 if( count >= 100)
7919                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7920                          /* this is used to judge if draw claims are legal */
7921                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7922                          if(engineOpponent) {
7923                            SendToProgram("force\n", engineOpponent); // suppress reply
7924                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7925                          }
7926                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7927                          return 1;
7928                 }
7929
7930                 /* if draw offer is pending, treat it as a draw claim
7931                  * when draw condition present, to allow engines a way to
7932                  * claim draws before making their move to avoid a race
7933                  * condition occurring after their move
7934                  */
7935                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7936                          char *p = NULL;
7937                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7938                              p = "Draw claim: 50-move rule";
7939                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7940                              p = "Draw claim: 3-fold repetition";
7941                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7942                              p = "Draw claim: insufficient mating material";
7943                          if( p != NULL && canAdjudicate) {
7944                              if(engineOpponent) {
7945                                SendToProgram("force\n", engineOpponent); // suppress reply
7946                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7947                              }
7948                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7949                              return 1;
7950                          }
7951                 }
7952
7953                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7954                     if(engineOpponent) {
7955                       SendToProgram("force\n", engineOpponent); // suppress reply
7956                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7957                     }
7958                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7959                     return 1;
7960                 }
7961         return 0;
7962 }
7963
7964 char *
7965 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7966 {   // [HGM] book: this routine intercepts moves to simulate book replies
7967     char *bookHit = NULL;
7968
7969     //first determine if the incoming move brings opponent into his book
7970     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7971         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7972     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7973     if(bookHit != NULL && !cps->bookSuspend) {
7974         // make sure opponent is not going to reply after receiving move to book position
7975         SendToProgram("force\n", cps);
7976         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7977     }
7978     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7979     // now arrange restart after book miss
7980     if(bookHit) {
7981         // after a book hit we never send 'go', and the code after the call to this routine
7982         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7983         char buf[MSG_SIZ], *move = bookHit;
7984         if(cps->useSAN) {
7985             int fromX, fromY, toX, toY;
7986             char promoChar;
7987             ChessMove moveType;
7988             move = buf + 30;
7989             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7990                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7991                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7992                                     PosFlags(forwardMostMove),
7993                                     fromY, fromX, toY, toX, promoChar, move);
7994             } else {
7995                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7996                 bookHit = NULL;
7997             }
7998         }
7999         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8000         SendToProgram(buf, cps);
8001         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8002     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8003         SendToProgram("go\n", cps);
8004         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8005     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8006         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8007             SendToProgram("go\n", cps);
8008         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8009     }
8010     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8011 }
8012
8013 int
8014 LoadError (char *errmess, ChessProgramState *cps)
8015 {   // unloads engine and switches back to -ncp mode if it was first
8016     if(cps->initDone) return FALSE;
8017     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8018     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8019     cps->pr = NoProc; 
8020     if(cps == &first) {
8021         appData.noChessProgram = TRUE;
8022         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8023         gameMode = BeginningOfGame; ModeHighlight();
8024         SetNCPMode();
8025     }
8026     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8027     DisplayMessage("", ""); // erase waiting message
8028     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8029     return TRUE;
8030 }
8031
8032 char *savedMessage;
8033 ChessProgramState *savedState;
8034 void
8035 DeferredBookMove (void)
8036 {
8037         if(savedState->lastPing != savedState->lastPong)
8038                     ScheduleDelayedEvent(DeferredBookMove, 10);
8039         else
8040         HandleMachineMove(savedMessage, savedState);
8041 }
8042
8043 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8044 static ChessProgramState *stalledEngine;
8045 static char stashedInputMove[MSG_SIZ];
8046
8047 void
8048 HandleMachineMove (char *message, ChessProgramState *cps)
8049 {
8050     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8051     char realname[MSG_SIZ];
8052     int fromX, fromY, toX, toY;
8053     ChessMove moveType;
8054     char promoChar;
8055     char *p, *pv=buf1;
8056     int machineWhite, oldError;
8057     char *bookHit;
8058
8059     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8060         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8061         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8062             DisplayError(_("Invalid pairing from pairing engine"), 0);
8063             return;
8064         }
8065         pairingReceived = 1;
8066         NextMatchGame();
8067         return; // Skim the pairing messages here.
8068     }
8069
8070     oldError = cps->userError; cps->userError = 0;
8071
8072 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8073     /*
8074      * Kludge to ignore BEL characters
8075      */
8076     while (*message == '\007') message++;
8077
8078     /*
8079      * [HGM] engine debug message: ignore lines starting with '#' character
8080      */
8081     if(cps->debug && *message == '#') return;
8082
8083     /*
8084      * Look for book output
8085      */
8086     if (cps == &first && bookRequested) {
8087         if (message[0] == '\t' || message[0] == ' ') {
8088             /* Part of the book output is here; append it */
8089             strcat(bookOutput, message);
8090             strcat(bookOutput, "  \n");
8091             return;
8092         } else if (bookOutput[0] != NULLCHAR) {
8093             /* All of book output has arrived; display it */
8094             char *p = bookOutput;
8095             while (*p != NULLCHAR) {
8096                 if (*p == '\t') *p = ' ';
8097                 p++;
8098             }
8099             DisplayInformation(bookOutput);
8100             bookRequested = FALSE;
8101             /* Fall through to parse the current output */
8102         }
8103     }
8104
8105     /*
8106      * Look for machine move.
8107      */
8108     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8109         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8110     {
8111         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8112             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8113             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8114             stalledEngine = cps;
8115             if(appData.ponderNextMove) { // bring opponent out of ponder
8116                 if(gameMode == TwoMachinesPlay) {
8117                     if(cps->other->pause)
8118                         PauseEngine(cps->other);
8119                     else
8120                         SendToProgram("easy\n", cps->other);
8121                 }
8122             }
8123             StopClocks();
8124             return;
8125         }
8126
8127         /* This method is only useful on engines that support ping */
8128         if (cps->lastPing != cps->lastPong) {
8129           if (gameMode == BeginningOfGame) {
8130             /* Extra move from before last new; ignore */
8131             if (appData.debugMode) {
8132                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8133             }
8134           } else {
8135             if (appData.debugMode) {
8136                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8137                         cps->which, gameMode);
8138             }
8139
8140             SendToProgram("undo\n", cps);
8141           }
8142           return;
8143         }
8144
8145         switch (gameMode) {
8146           case BeginningOfGame:
8147             /* Extra move from before last reset; ignore */
8148             if (appData.debugMode) {
8149                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8150             }
8151             return;
8152
8153           case EndOfGame:
8154           case IcsIdle:
8155           default:
8156             /* Extra move after we tried to stop.  The mode test is
8157                not a reliable way of detecting this problem, but it's
8158                the best we can do on engines that don't support ping.
8159             */
8160             if (appData.debugMode) {
8161                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8162                         cps->which, gameMode);
8163             }
8164             SendToProgram("undo\n", cps);
8165             return;
8166
8167           case MachinePlaysWhite:
8168           case IcsPlayingWhite:
8169             machineWhite = TRUE;
8170             break;
8171
8172           case MachinePlaysBlack:
8173           case IcsPlayingBlack:
8174             machineWhite = FALSE;
8175             break;
8176
8177           case TwoMachinesPlay:
8178             machineWhite = (cps->twoMachinesColor[0] == 'w');
8179             break;
8180         }
8181         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8182             if (appData.debugMode) {
8183                 fprintf(debugFP,
8184                         "Ignoring move out of turn by %s, gameMode %d"
8185                         ", forwardMost %d\n",
8186                         cps->which, gameMode, forwardMostMove);
8187             }
8188             return;
8189         }
8190
8191         if(cps->alphaRank) AlphaRank(machineMove, 4);
8192         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8193                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8194             /* Machine move could not be parsed; ignore it. */
8195           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8196                     machineMove, _(cps->which));
8197             DisplayError(buf1, 0);
8198             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8199                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8200             if (gameMode == TwoMachinesPlay) {
8201               GameEnds(machineWhite ? BlackWins : WhiteWins,
8202                        buf1, GE_XBOARD);
8203             }
8204             return;
8205         }
8206
8207         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8208         /* So we have to redo legality test with true e.p. status here,  */
8209         /* to make sure an illegal e.p. capture does not slip through,   */
8210         /* to cause a forfeit on a justified illegal-move complaint      */
8211         /* of the opponent.                                              */
8212         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8213            ChessMove moveType;
8214            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8215                              fromY, fromX, toY, toX, promoChar);
8216             if(moveType == IllegalMove) {
8217               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8218                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8219                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8220                            buf1, GE_XBOARD);
8221                 return;
8222            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8223            /* [HGM] Kludge to handle engines that send FRC-style castling
8224               when they shouldn't (like TSCP-Gothic) */
8225            switch(moveType) {
8226              case WhiteASideCastleFR:
8227              case BlackASideCastleFR:
8228                toX+=2;
8229                currentMoveString[2]++;
8230                break;
8231              case WhiteHSideCastleFR:
8232              case BlackHSideCastleFR:
8233                toX--;
8234                currentMoveString[2]--;
8235                break;
8236              default: ; // nothing to do, but suppresses warning of pedantic compilers
8237            }
8238         }
8239         hintRequested = FALSE;
8240         lastHint[0] = NULLCHAR;
8241         bookRequested = FALSE;
8242         /* Program may be pondering now */
8243         cps->maybeThinking = TRUE;
8244         if (cps->sendTime == 2) cps->sendTime = 1;
8245         if (cps->offeredDraw) cps->offeredDraw--;
8246
8247         /* [AS] Save move info*/
8248         pvInfoList[ forwardMostMove ].score = programStats.score;
8249         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8250         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8251
8252         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8253
8254         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8255         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8256             int count = 0;
8257
8258             while( count < adjudicateLossPlies ) {
8259                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8260
8261                 if( count & 1 ) {
8262                     score = -score; /* Flip score for winning side */
8263                 }
8264
8265                 if( score > adjudicateLossThreshold ) {
8266                     break;
8267                 }
8268
8269                 count++;
8270             }
8271
8272             if( count >= adjudicateLossPlies ) {
8273                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8274
8275                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8276                     "Xboard adjudication",
8277                     GE_XBOARD );
8278
8279                 return;
8280             }
8281         }
8282
8283         if(Adjudicate(cps)) {
8284             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8285             return; // [HGM] adjudicate: for all automatic game ends
8286         }
8287
8288 #if ZIPPY
8289         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8290             first.initDone) {
8291           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8292                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8293                 SendToICS("draw ");
8294                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8295           }
8296           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8297           ics_user_moved = 1;
8298           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8299                 char buf[3*MSG_SIZ];
8300
8301                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8302                         programStats.score / 100.,
8303                         programStats.depth,
8304                         programStats.time / 100.,
8305                         (unsigned int)programStats.nodes,
8306                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8307                         programStats.movelist);
8308                 SendToICS(buf);
8309 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8310           }
8311         }
8312 #endif
8313
8314         /* [AS] Clear stats for next move */
8315         ClearProgramStats();
8316         thinkOutput[0] = NULLCHAR;
8317         hiddenThinkOutputState = 0;
8318
8319         bookHit = NULL;
8320         if (gameMode == TwoMachinesPlay) {
8321             /* [HGM] relaying draw offers moved to after reception of move */
8322             /* and interpreting offer as claim if it brings draw condition */
8323             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8324                 SendToProgram("draw\n", cps->other);
8325             }
8326             if (cps->other->sendTime) {
8327                 SendTimeRemaining(cps->other,
8328                                   cps->other->twoMachinesColor[0] == 'w');
8329             }
8330             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8331             if (firstMove && !bookHit) {
8332                 firstMove = FALSE;
8333                 if (cps->other->useColors) {
8334                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8335                 }
8336                 SendToProgram("go\n", cps->other);
8337             }
8338             cps->other->maybeThinking = TRUE;
8339         }
8340
8341         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8342
8343         if (!pausing && appData.ringBellAfterMoves) {
8344             RingBell();
8345         }
8346
8347         /*
8348          * Reenable menu items that were disabled while
8349          * machine was thinking
8350          */
8351         if (gameMode != TwoMachinesPlay)
8352             SetUserThinkingEnables();
8353
8354         // [HGM] book: after book hit opponent has received move and is now in force mode
8355         // force the book reply into it, and then fake that it outputted this move by jumping
8356         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8357         if(bookHit) {
8358                 static char bookMove[MSG_SIZ]; // a bit generous?
8359
8360                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8361                 strcat(bookMove, bookHit);
8362                 message = bookMove;
8363                 cps = cps->other;
8364                 programStats.nodes = programStats.depth = programStats.time =
8365                 programStats.score = programStats.got_only_move = 0;
8366                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8367
8368                 if(cps->lastPing != cps->lastPong) {
8369                     savedMessage = message; // args for deferred call
8370                     savedState = cps;
8371                     ScheduleDelayedEvent(DeferredBookMove, 10);
8372                     return;
8373                 }
8374                 goto FakeBookMove;
8375         }
8376
8377         return;
8378     }
8379
8380     /* Set special modes for chess engines.  Later something general
8381      *  could be added here; for now there is just one kludge feature,
8382      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8383      *  when "xboard" is given as an interactive command.
8384      */
8385     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8386         cps->useSigint = FALSE;
8387         cps->useSigterm = FALSE;
8388     }
8389     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8390       ParseFeatures(message+8, cps);
8391       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8392     }
8393
8394     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8395                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8396       int dummy, s=6; char buf[MSG_SIZ];
8397       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8398       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8399       if(startedFromSetupPosition) return;
8400       ParseFEN(boards[0], &dummy, message+s);
8401       DrawPosition(TRUE, boards[0]);
8402       startedFromSetupPosition = TRUE;
8403       return;
8404     }
8405     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8406      * want this, I was asked to put it in, and obliged.
8407      */
8408     if (!strncmp(message, "setboard ", 9)) {
8409         Board initial_position;
8410
8411         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8412
8413         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8414             DisplayError(_("Bad FEN received from engine"), 0);
8415             return ;
8416         } else {
8417            Reset(TRUE, FALSE);
8418            CopyBoard(boards[0], initial_position);
8419            initialRulePlies = FENrulePlies;
8420            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8421            else gameMode = MachinePlaysBlack;
8422            DrawPosition(FALSE, boards[currentMove]);
8423         }
8424         return;
8425     }
8426
8427     /*
8428      * Look for communication commands
8429      */
8430     if (!strncmp(message, "telluser ", 9)) {
8431         if(message[9] == '\\' && message[10] == '\\')
8432             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8433         PlayTellSound();
8434         DisplayNote(message + 9);
8435         return;
8436     }
8437     if (!strncmp(message, "tellusererror ", 14)) {
8438         cps->userError = 1;
8439         if(message[14] == '\\' && message[15] == '\\')
8440             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8441         PlayTellSound();
8442         DisplayError(message + 14, 0);
8443         return;
8444     }
8445     if (!strncmp(message, "tellopponent ", 13)) {
8446       if (appData.icsActive) {
8447         if (loggedOn) {
8448           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8449           SendToICS(buf1);
8450         }
8451       } else {
8452         DisplayNote(message + 13);
8453       }
8454       return;
8455     }
8456     if (!strncmp(message, "tellothers ", 11)) {
8457       if (appData.icsActive) {
8458         if (loggedOn) {
8459           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8460           SendToICS(buf1);
8461         }
8462       }
8463       return;
8464     }
8465     if (!strncmp(message, "tellall ", 8)) {
8466       if (appData.icsActive) {
8467         if (loggedOn) {
8468           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8469           SendToICS(buf1);
8470         }
8471       } else {
8472         DisplayNote(message + 8);
8473       }
8474       return;
8475     }
8476     if (strncmp(message, "warning", 7) == 0) {
8477         /* Undocumented feature, use tellusererror in new code */
8478         DisplayError(message, 0);
8479         return;
8480     }
8481     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8482         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8483         strcat(realname, " query");
8484         AskQuestion(realname, buf2, buf1, cps->pr);
8485         return;
8486     }
8487     /* Commands from the engine directly to ICS.  We don't allow these to be
8488      *  sent until we are logged on. Crafty kibitzes have been known to
8489      *  interfere with the login process.
8490      */
8491     if (loggedOn) {
8492         if (!strncmp(message, "tellics ", 8)) {
8493             SendToICS(message + 8);
8494             SendToICS("\n");
8495             return;
8496         }
8497         if (!strncmp(message, "tellicsnoalias ", 15)) {
8498             SendToICS(ics_prefix);
8499             SendToICS(message + 15);
8500             SendToICS("\n");
8501             return;
8502         }
8503         /* The following are for backward compatibility only */
8504         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8505             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8506             SendToICS(ics_prefix);
8507             SendToICS(message);
8508             SendToICS("\n");
8509             return;
8510         }
8511     }
8512     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8513         return;
8514     }
8515     /*
8516      * If the move is illegal, cancel it and redraw the board.
8517      * Also deal with other error cases.  Matching is rather loose
8518      * here to accommodate engines written before the spec.
8519      */
8520     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8521         strncmp(message, "Error", 5) == 0) {
8522         if (StrStr(message, "name") ||
8523             StrStr(message, "rating") || StrStr(message, "?") ||
8524             StrStr(message, "result") || StrStr(message, "board") ||
8525             StrStr(message, "bk") || StrStr(message, "computer") ||
8526             StrStr(message, "variant") || StrStr(message, "hint") ||
8527             StrStr(message, "random") || StrStr(message, "depth") ||
8528             StrStr(message, "accepted")) {
8529             return;
8530         }
8531         if (StrStr(message, "protover")) {
8532           /* Program is responding to input, so it's apparently done
8533              initializing, and this error message indicates it is
8534              protocol version 1.  So we don't need to wait any longer
8535              for it to initialize and send feature commands. */
8536           FeatureDone(cps, 1);
8537           cps->protocolVersion = 1;
8538           return;
8539         }
8540         cps->maybeThinking = FALSE;
8541
8542         if (StrStr(message, "draw")) {
8543             /* Program doesn't have "draw" command */
8544             cps->sendDrawOffers = 0;
8545             return;
8546         }
8547         if (cps->sendTime != 1 &&
8548             (StrStr(message, "time") || StrStr(message, "otim"))) {
8549           /* Program apparently doesn't have "time" or "otim" command */
8550           cps->sendTime = 0;
8551           return;
8552         }
8553         if (StrStr(message, "analyze")) {
8554             cps->analysisSupport = FALSE;
8555             cps->analyzing = FALSE;
8556 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8557             EditGameEvent(); // [HGM] try to preserve loaded game
8558             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8559             DisplayError(buf2, 0);
8560             return;
8561         }
8562         if (StrStr(message, "(no matching move)st")) {
8563           /* Special kludge for GNU Chess 4 only */
8564           cps->stKludge = TRUE;
8565           SendTimeControl(cps, movesPerSession, timeControl,
8566                           timeIncrement, appData.searchDepth,
8567                           searchTime);
8568           return;
8569         }
8570         if (StrStr(message, "(no matching move)sd")) {
8571           /* Special kludge for GNU Chess 4 only */
8572           cps->sdKludge = TRUE;
8573           SendTimeControl(cps, movesPerSession, timeControl,
8574                           timeIncrement, appData.searchDepth,
8575                           searchTime);
8576           return;
8577         }
8578         if (!StrStr(message, "llegal")) {
8579             return;
8580         }
8581         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8582             gameMode == IcsIdle) return;
8583         if (forwardMostMove <= backwardMostMove) return;
8584         if (pausing) PauseEvent();
8585       if(appData.forceIllegal) {
8586             // [HGM] illegal: machine refused move; force position after move into it
8587           SendToProgram("force\n", cps);
8588           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8589                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8590                 // when black is to move, while there might be nothing on a2 or black
8591                 // might already have the move. So send the board as if white has the move.
8592                 // But first we must change the stm of the engine, as it refused the last move
8593                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8594                 if(WhiteOnMove(forwardMostMove)) {
8595                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8596                     SendBoard(cps, forwardMostMove); // kludgeless board
8597                 } else {
8598                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8599                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8600                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8601                 }
8602           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8603             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8604                  gameMode == TwoMachinesPlay)
8605               SendToProgram("go\n", cps);
8606             return;
8607       } else
8608         if (gameMode == PlayFromGameFile) {
8609             /* Stop reading this game file */
8610             gameMode = EditGame;
8611             ModeHighlight();
8612         }
8613         /* [HGM] illegal-move claim should forfeit game when Xboard */
8614         /* only passes fully legal moves                            */
8615         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8616             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8617                                 "False illegal-move claim", GE_XBOARD );
8618             return; // do not take back move we tested as valid
8619         }
8620         currentMove = forwardMostMove-1;
8621         DisplayMove(currentMove-1); /* before DisplayMoveError */
8622         SwitchClocks(forwardMostMove-1); // [HGM] race
8623         DisplayBothClocks();
8624         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8625                 parseList[currentMove], _(cps->which));
8626         DisplayMoveError(buf1);
8627         DrawPosition(FALSE, boards[currentMove]);
8628
8629         SetUserThinkingEnables();
8630         return;
8631     }
8632     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8633         /* Program has a broken "time" command that
8634            outputs a string not ending in newline.
8635            Don't use it. */
8636         cps->sendTime = 0;
8637     }
8638
8639     /*
8640      * If chess program startup fails, exit with an error message.
8641      * Attempts to recover here are futile. [HGM] Well, we try anyway
8642      */
8643     if ((StrStr(message, "unknown host") != NULL)
8644         || (StrStr(message, "No remote directory") != NULL)
8645         || (StrStr(message, "not found") != NULL)
8646         || (StrStr(message, "No such file") != NULL)
8647         || (StrStr(message, "can't alloc") != NULL)
8648         || (StrStr(message, "Permission denied") != NULL)) {
8649
8650         cps->maybeThinking = FALSE;
8651         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8652                 _(cps->which), cps->program, cps->host, message);
8653         RemoveInputSource(cps->isr);
8654         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8655             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8656             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8657         }
8658         return;
8659     }
8660
8661     /*
8662      * Look for hint output
8663      */
8664     if (sscanf(message, "Hint: %s", buf1) == 1) {
8665         if (cps == &first && hintRequested) {
8666             hintRequested = FALSE;
8667             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8668                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8669                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8670                                     PosFlags(forwardMostMove),
8671                                     fromY, fromX, toY, toX, promoChar, buf1);
8672                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8673                 DisplayInformation(buf2);
8674             } else {
8675                 /* Hint move could not be parsed!? */
8676               snprintf(buf2, sizeof(buf2),
8677                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8678                         buf1, _(cps->which));
8679                 DisplayError(buf2, 0);
8680             }
8681         } else {
8682           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8683         }
8684         return;
8685     }
8686
8687     /*
8688      * Ignore other messages if game is not in progress
8689      */
8690     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8691         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8692
8693     /*
8694      * look for win, lose, draw, or draw offer
8695      */
8696     if (strncmp(message, "1-0", 3) == 0) {
8697         char *p, *q, *r = "";
8698         p = strchr(message, '{');
8699         if (p) {
8700             q = strchr(p, '}');
8701             if (q) {
8702                 *q = NULLCHAR;
8703                 r = p + 1;
8704             }
8705         }
8706         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8707         return;
8708     } else if (strncmp(message, "0-1", 3) == 0) {
8709         char *p, *q, *r = "";
8710         p = strchr(message, '{');
8711         if (p) {
8712             q = strchr(p, '}');
8713             if (q) {
8714                 *q = NULLCHAR;
8715                 r = p + 1;
8716             }
8717         }
8718         /* Kludge for Arasan 4.1 bug */
8719         if (strcmp(r, "Black resigns") == 0) {
8720             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8721             return;
8722         }
8723         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8724         return;
8725     } else if (strncmp(message, "1/2", 3) == 0) {
8726         char *p, *q, *r = "";
8727         p = strchr(message, '{');
8728         if (p) {
8729             q = strchr(p, '}');
8730             if (q) {
8731                 *q = NULLCHAR;
8732                 r = p + 1;
8733             }
8734         }
8735
8736         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8737         return;
8738
8739     } else if (strncmp(message, "White resign", 12) == 0) {
8740         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8741         return;
8742     } else if (strncmp(message, "Black resign", 12) == 0) {
8743         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8744         return;
8745     } else if (strncmp(message, "White matches", 13) == 0 ||
8746                strncmp(message, "Black matches", 13) == 0   ) {
8747         /* [HGM] ignore GNUShogi noises */
8748         return;
8749     } else if (strncmp(message, "White", 5) == 0 &&
8750                message[5] != '(' &&
8751                StrStr(message, "Black") == NULL) {
8752         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8753         return;
8754     } else if (strncmp(message, "Black", 5) == 0 &&
8755                message[5] != '(') {
8756         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8757         return;
8758     } else if (strcmp(message, "resign") == 0 ||
8759                strcmp(message, "computer resigns") == 0) {
8760         switch (gameMode) {
8761           case MachinePlaysBlack:
8762           case IcsPlayingBlack:
8763             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8764             break;
8765           case MachinePlaysWhite:
8766           case IcsPlayingWhite:
8767             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8768             break;
8769           case TwoMachinesPlay:
8770             if (cps->twoMachinesColor[0] == 'w')
8771               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8772             else
8773               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8774             break;
8775           default:
8776             /* can't happen */
8777             break;
8778         }
8779         return;
8780     } else if (strncmp(message, "opponent mates", 14) == 0) {
8781         switch (gameMode) {
8782           case MachinePlaysBlack:
8783           case IcsPlayingBlack:
8784             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8785             break;
8786           case MachinePlaysWhite:
8787           case IcsPlayingWhite:
8788             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8789             break;
8790           case TwoMachinesPlay:
8791             if (cps->twoMachinesColor[0] == 'w')
8792               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8793             else
8794               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8795             break;
8796           default:
8797             /* can't happen */
8798             break;
8799         }
8800         return;
8801     } else if (strncmp(message, "computer mates", 14) == 0) {
8802         switch (gameMode) {
8803           case MachinePlaysBlack:
8804           case IcsPlayingBlack:
8805             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8806             break;
8807           case MachinePlaysWhite:
8808           case IcsPlayingWhite:
8809             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8810             break;
8811           case TwoMachinesPlay:
8812             if (cps->twoMachinesColor[0] == 'w')
8813               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8814             else
8815               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8816             break;
8817           default:
8818             /* can't happen */
8819             break;
8820         }
8821         return;
8822     } else if (strncmp(message, "checkmate", 9) == 0) {
8823         if (WhiteOnMove(forwardMostMove)) {
8824             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8825         } else {
8826             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8827         }
8828         return;
8829     } else if (strstr(message, "Draw") != NULL ||
8830                strstr(message, "game is a draw") != NULL) {
8831         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8832         return;
8833     } else if (strstr(message, "offer") != NULL &&
8834                strstr(message, "draw") != NULL) {
8835 #if ZIPPY
8836         if (appData.zippyPlay && first.initDone) {
8837             /* Relay offer to ICS */
8838             SendToICS(ics_prefix);
8839             SendToICS("draw\n");
8840         }
8841 #endif
8842         cps->offeredDraw = 2; /* valid until this engine moves twice */
8843         if (gameMode == TwoMachinesPlay) {
8844             if (cps->other->offeredDraw) {
8845                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8846             /* [HGM] in two-machine mode we delay relaying draw offer      */
8847             /* until after we also have move, to see if it is really claim */
8848             }
8849         } else if (gameMode == MachinePlaysWhite ||
8850                    gameMode == MachinePlaysBlack) {
8851           if (userOfferedDraw) {
8852             DisplayInformation(_("Machine accepts your draw offer"));
8853             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8854           } else {
8855             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8856           }
8857         }
8858     }
8859
8860
8861     /*
8862      * Look for thinking output
8863      */
8864     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8865           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8866                                 ) {
8867         int plylev, mvleft, mvtot, curscore, time;
8868         char mvname[MOVE_LEN];
8869         u64 nodes; // [DM]
8870         char plyext;
8871         int ignore = FALSE;
8872         int prefixHint = FALSE;
8873         mvname[0] = NULLCHAR;
8874
8875         switch (gameMode) {
8876           case MachinePlaysBlack:
8877           case IcsPlayingBlack:
8878             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8879             break;
8880           case MachinePlaysWhite:
8881           case IcsPlayingWhite:
8882             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8883             break;
8884           case AnalyzeMode:
8885           case AnalyzeFile:
8886             break;
8887           case IcsObserving: /* [DM] icsEngineAnalyze */
8888             if (!appData.icsEngineAnalyze) ignore = TRUE;
8889             break;
8890           case TwoMachinesPlay:
8891             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8892                 ignore = TRUE;
8893             }
8894             break;
8895           default:
8896             ignore = TRUE;
8897             break;
8898         }
8899
8900         if (!ignore) {
8901             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8902             buf1[0] = NULLCHAR;
8903             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8904                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8905
8906                 if (plyext != ' ' && plyext != '\t') {
8907                     time *= 100;
8908                 }
8909
8910                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8911                 if( cps->scoreIsAbsolute &&
8912                     ( gameMode == MachinePlaysBlack ||
8913                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8914                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8915                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8916                      !WhiteOnMove(currentMove)
8917                     ) )
8918                 {
8919                     curscore = -curscore;
8920                 }
8921
8922                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8923
8924                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8925                         char buf[MSG_SIZ];
8926                         FILE *f;
8927                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8928                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8929                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8930                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8931                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8932                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8933                                 fclose(f);
8934                         } else DisplayError(_("failed writing PV"), 0);
8935                 }
8936
8937                 tempStats.depth = plylev;
8938                 tempStats.nodes = nodes;
8939                 tempStats.time = time;
8940                 tempStats.score = curscore;
8941                 tempStats.got_only_move = 0;
8942
8943                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8944                         int ticklen;
8945
8946                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8947                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8948                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8949                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8950                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8951                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8952                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8953                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8954                 }
8955
8956                 /* Buffer overflow protection */
8957                 if (pv[0] != NULLCHAR) {
8958                     if (strlen(pv) >= sizeof(tempStats.movelist)
8959                         && appData.debugMode) {
8960                         fprintf(debugFP,
8961                                 "PV is too long; using the first %u bytes.\n",
8962                                 (unsigned) sizeof(tempStats.movelist) - 1);
8963                     }
8964
8965                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8966                 } else {
8967                     sprintf(tempStats.movelist, " no PV\n");
8968                 }
8969
8970                 if (tempStats.seen_stat) {
8971                     tempStats.ok_to_send = 1;
8972                 }
8973
8974                 if (strchr(tempStats.movelist, '(') != NULL) {
8975                     tempStats.line_is_book = 1;
8976                     tempStats.nr_moves = 0;
8977                     tempStats.moves_left = 0;
8978                 } else {
8979                     tempStats.line_is_book = 0;
8980                 }
8981
8982                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8983                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8984
8985                 SendProgramStatsToFrontend( cps, &tempStats );
8986
8987                 /*
8988                     [AS] Protect the thinkOutput buffer from overflow... this
8989                     is only useful if buf1 hasn't overflowed first!
8990                 */
8991                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8992                          plylev,
8993                          (gameMode == TwoMachinesPlay ?
8994                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8995                          ((double) curscore) / 100.0,
8996                          prefixHint ? lastHint : "",
8997                          prefixHint ? " " : "" );
8998
8999                 if( buf1[0] != NULLCHAR ) {
9000                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9001
9002                     if( strlen(pv) > max_len ) {
9003                         if( appData.debugMode) {
9004                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9005                         }
9006                         pv[max_len+1] = '\0';
9007                     }
9008
9009                     strcat( thinkOutput, pv);
9010                 }
9011
9012                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9013                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9014                     DisplayMove(currentMove - 1);
9015                 }
9016                 return;
9017
9018             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9019                 /* crafty (9.25+) says "(only move) <move>"
9020                  * if there is only 1 legal move
9021                  */
9022                 sscanf(p, "(only move) %s", buf1);
9023                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9024                 sprintf(programStats.movelist, "%s (only move)", buf1);
9025                 programStats.depth = 1;
9026                 programStats.nr_moves = 1;
9027                 programStats.moves_left = 1;
9028                 programStats.nodes = 1;
9029                 programStats.time = 1;
9030                 programStats.got_only_move = 1;
9031
9032                 /* Not really, but we also use this member to
9033                    mean "line isn't going to change" (Crafty
9034                    isn't searching, so stats won't change) */
9035                 programStats.line_is_book = 1;
9036
9037                 SendProgramStatsToFrontend( cps, &programStats );
9038
9039                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9040                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9041                     DisplayMove(currentMove - 1);
9042                 }
9043                 return;
9044             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9045                               &time, &nodes, &plylev, &mvleft,
9046                               &mvtot, mvname) >= 5) {
9047                 /* The stat01: line is from Crafty (9.29+) in response
9048                    to the "." command */
9049                 programStats.seen_stat = 1;
9050                 cps->maybeThinking = TRUE;
9051
9052                 if (programStats.got_only_move || !appData.periodicUpdates)
9053                   return;
9054
9055                 programStats.depth = plylev;
9056                 programStats.time = time;
9057                 programStats.nodes = nodes;
9058                 programStats.moves_left = mvleft;
9059                 programStats.nr_moves = mvtot;
9060                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9061                 programStats.ok_to_send = 1;
9062                 programStats.movelist[0] = '\0';
9063
9064                 SendProgramStatsToFrontend( cps, &programStats );
9065
9066                 return;
9067
9068             } else if (strncmp(message,"++",2) == 0) {
9069                 /* Crafty 9.29+ outputs this */
9070                 programStats.got_fail = 2;
9071                 return;
9072
9073             } else if (strncmp(message,"--",2) == 0) {
9074                 /* Crafty 9.29+ outputs this */
9075                 programStats.got_fail = 1;
9076                 return;
9077
9078             } else if (thinkOutput[0] != NULLCHAR &&
9079                        strncmp(message, "    ", 4) == 0) {
9080                 unsigned message_len;
9081
9082                 p = message;
9083                 while (*p && *p == ' ') p++;
9084
9085                 message_len = strlen( p );
9086
9087                 /* [AS] Avoid buffer overflow */
9088                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9089                     strcat(thinkOutput, " ");
9090                     strcat(thinkOutput, p);
9091                 }
9092
9093                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9094                     strcat(programStats.movelist, " ");
9095                     strcat(programStats.movelist, p);
9096                 }
9097
9098                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9099                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9100                     DisplayMove(currentMove - 1);
9101                 }
9102                 return;
9103             }
9104         }
9105         else {
9106             buf1[0] = NULLCHAR;
9107
9108             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9109                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9110             {
9111                 ChessProgramStats cpstats;
9112
9113                 if (plyext != ' ' && plyext != '\t') {
9114                     time *= 100;
9115                 }
9116
9117                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9118                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9119                     curscore = -curscore;
9120                 }
9121
9122                 cpstats.depth = plylev;
9123                 cpstats.nodes = nodes;
9124                 cpstats.time = time;
9125                 cpstats.score = curscore;
9126                 cpstats.got_only_move = 0;
9127                 cpstats.movelist[0] = '\0';
9128
9129                 if (buf1[0] != NULLCHAR) {
9130                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9131                 }
9132
9133                 cpstats.ok_to_send = 0;
9134                 cpstats.line_is_book = 0;
9135                 cpstats.nr_moves = 0;
9136                 cpstats.moves_left = 0;
9137
9138                 SendProgramStatsToFrontend( cps, &cpstats );
9139             }
9140         }
9141     }
9142 }
9143
9144
9145 /* Parse a game score from the character string "game", and
9146    record it as the history of the current game.  The game
9147    score is NOT assumed to start from the standard position.
9148    The display is not updated in any way.
9149    */
9150 void
9151 ParseGameHistory (char *game)
9152 {
9153     ChessMove moveType;
9154     int fromX, fromY, toX, toY, boardIndex;
9155     char promoChar;
9156     char *p, *q;
9157     char buf[MSG_SIZ];
9158
9159     if (appData.debugMode)
9160       fprintf(debugFP, "Parsing game history: %s\n", game);
9161
9162     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9163     gameInfo.site = StrSave(appData.icsHost);
9164     gameInfo.date = PGNDate();
9165     gameInfo.round = StrSave("-");
9166
9167     /* Parse out names of players */
9168     while (*game == ' ') game++;
9169     p = buf;
9170     while (*game != ' ') *p++ = *game++;
9171     *p = NULLCHAR;
9172     gameInfo.white = StrSave(buf);
9173     while (*game == ' ') game++;
9174     p = buf;
9175     while (*game != ' ' && *game != '\n') *p++ = *game++;
9176     *p = NULLCHAR;
9177     gameInfo.black = StrSave(buf);
9178
9179     /* Parse moves */
9180     boardIndex = blackPlaysFirst ? 1 : 0;
9181     yynewstr(game);
9182     for (;;) {
9183         yyboardindex = boardIndex;
9184         moveType = (ChessMove) Myylex();
9185         switch (moveType) {
9186           case IllegalMove:             /* maybe suicide chess, etc. */
9187   if (appData.debugMode) {
9188     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9189     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9190     setbuf(debugFP, NULL);
9191   }
9192           case WhitePromotion:
9193           case BlackPromotion:
9194           case WhiteNonPromotion:
9195           case BlackNonPromotion:
9196           case NormalMove:
9197           case WhiteCapturesEnPassant:
9198           case BlackCapturesEnPassant:
9199           case WhiteKingSideCastle:
9200           case WhiteQueenSideCastle:
9201           case BlackKingSideCastle:
9202           case BlackQueenSideCastle:
9203           case WhiteKingSideCastleWild:
9204           case WhiteQueenSideCastleWild:
9205           case BlackKingSideCastleWild:
9206           case BlackQueenSideCastleWild:
9207           /* PUSH Fabien */
9208           case WhiteHSideCastleFR:
9209           case WhiteASideCastleFR:
9210           case BlackHSideCastleFR:
9211           case BlackASideCastleFR:
9212           /* POP Fabien */
9213             fromX = currentMoveString[0] - AAA;
9214             fromY = currentMoveString[1] - ONE;
9215             toX = currentMoveString[2] - AAA;
9216             toY = currentMoveString[3] - ONE;
9217             promoChar = currentMoveString[4];
9218             break;
9219           case WhiteDrop:
9220           case BlackDrop:
9221             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9222             fromX = moveType == WhiteDrop ?
9223               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9224             (int) CharToPiece(ToLower(currentMoveString[0]));
9225             fromY = DROP_RANK;
9226             toX = currentMoveString[2] - AAA;
9227             toY = currentMoveString[3] - ONE;
9228             promoChar = NULLCHAR;
9229             break;
9230           case AmbiguousMove:
9231             /* bug? */
9232             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9233   if (appData.debugMode) {
9234     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9235     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9236     setbuf(debugFP, NULL);
9237   }
9238             DisplayError(buf, 0);
9239             return;
9240           case ImpossibleMove:
9241             /* bug? */
9242             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9243   if (appData.debugMode) {
9244     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9246     setbuf(debugFP, NULL);
9247   }
9248             DisplayError(buf, 0);
9249             return;
9250           case EndOfFile:
9251             if (boardIndex < backwardMostMove) {
9252                 /* Oops, gap.  How did that happen? */
9253                 DisplayError(_("Gap in move list"), 0);
9254                 return;
9255             }
9256             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9257             if (boardIndex > forwardMostMove) {
9258                 forwardMostMove = boardIndex;
9259             }
9260             return;
9261           case ElapsedTime:
9262             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9263                 strcat(parseList[boardIndex-1], " ");
9264                 strcat(parseList[boardIndex-1], yy_text);
9265             }
9266             continue;
9267           case Comment:
9268           case PGNTag:
9269           case NAG:
9270           default:
9271             /* ignore */
9272             continue;
9273           case WhiteWins:
9274           case BlackWins:
9275           case GameIsDrawn:
9276           case GameUnfinished:
9277             if (gameMode == IcsExamining) {
9278                 if (boardIndex < backwardMostMove) {
9279                     /* Oops, gap.  How did that happen? */
9280                     return;
9281                 }
9282                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9283                 return;
9284             }
9285             gameInfo.result = moveType;
9286             p = strchr(yy_text, '{');
9287             if (p == NULL) p = strchr(yy_text, '(');
9288             if (p == NULL) {
9289                 p = yy_text;
9290                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9291             } else {
9292                 q = strchr(p, *p == '{' ? '}' : ')');
9293                 if (q != NULL) *q = NULLCHAR;
9294                 p++;
9295             }
9296             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9297             gameInfo.resultDetails = StrSave(p);
9298             continue;
9299         }
9300         if (boardIndex >= forwardMostMove &&
9301             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9302             backwardMostMove = blackPlaysFirst ? 1 : 0;
9303             return;
9304         }
9305         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9306                                  fromY, fromX, toY, toX, promoChar,
9307                                  parseList[boardIndex]);
9308         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9309         /* currentMoveString is set as a side-effect of yylex */
9310         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9311         strcat(moveList[boardIndex], "\n");
9312         boardIndex++;
9313         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9314         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9315           case MT_NONE:
9316           case MT_STALEMATE:
9317           default:
9318             break;
9319           case MT_CHECK:
9320             if(gameInfo.variant != VariantShogi)
9321                 strcat(parseList[boardIndex - 1], "+");
9322             break;
9323           case MT_CHECKMATE:
9324           case MT_STAINMATE:
9325             strcat(parseList[boardIndex - 1], "#");
9326             break;
9327         }
9328     }
9329 }
9330
9331
9332 /* Apply a move to the given board  */
9333 void
9334 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9335 {
9336   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9337   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9338
9339     /* [HGM] compute & store e.p. status and castling rights for new position */
9340     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9341
9342       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9343       oldEP = (signed char)board[EP_STATUS];
9344       board[EP_STATUS] = EP_NONE;
9345
9346   if (fromY == DROP_RANK) {
9347         /* must be first */
9348         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9349             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9350             return;
9351         }
9352         piece = board[toY][toX] = (ChessSquare) fromX;
9353   } else {
9354       int i;
9355
9356       if( board[toY][toX] != EmptySquare )
9357            board[EP_STATUS] = EP_CAPTURE;
9358
9359       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9360            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9361                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9362       } else
9363       if( board[fromY][fromX] == WhitePawn ) {
9364            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9365                board[EP_STATUS] = EP_PAWN_MOVE;
9366            if( toY-fromY==2) {
9367                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9368                         gameInfo.variant != VariantBerolina || toX < fromX)
9369                       board[EP_STATUS] = toX | berolina;
9370                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9371                         gameInfo.variant != VariantBerolina || toX > fromX)
9372                       board[EP_STATUS] = toX;
9373            }
9374       } else
9375       if( board[fromY][fromX] == BlackPawn ) {
9376            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9377                board[EP_STATUS] = EP_PAWN_MOVE;
9378            if( toY-fromY== -2) {
9379                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9380                         gameInfo.variant != VariantBerolina || toX < fromX)
9381                       board[EP_STATUS] = toX | berolina;
9382                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9383                         gameInfo.variant != VariantBerolina || toX > fromX)
9384                       board[EP_STATUS] = toX;
9385            }
9386        }
9387
9388        for(i=0; i<nrCastlingRights; i++) {
9389            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9390               board[CASTLING][i] == toX   && castlingRank[i] == toY
9391              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9392        }
9393
9394        if(gameInfo.variant == VariantSChess) { // update virginity
9395            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9396            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9397            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9398            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9399        }
9400
9401      if (fromX == toX && fromY == toY) return;
9402
9403      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9404      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9405      if(gameInfo.variant == VariantKnightmate)
9406          king += (int) WhiteUnicorn - (int) WhiteKing;
9407
9408     /* Code added by Tord: */
9409     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9410     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9411         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9412       board[fromY][fromX] = EmptySquare;
9413       board[toY][toX] = EmptySquare;
9414       if((toX > fromX) != (piece == WhiteRook)) {
9415         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9416       } else {
9417         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9418       }
9419     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9420                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9421       board[fromY][fromX] = EmptySquare;
9422       board[toY][toX] = EmptySquare;
9423       if((toX > fromX) != (piece == BlackRook)) {
9424         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9425       } else {
9426         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9427       }
9428     /* End of code added by Tord */
9429
9430     } else if (board[fromY][fromX] == king
9431         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9432         && toY == fromY && toX > fromX+1) {
9433         board[fromY][fromX] = EmptySquare;
9434         board[toY][toX] = king;
9435         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9436         board[fromY][BOARD_RGHT-1] = EmptySquare;
9437     } else if (board[fromY][fromX] == king
9438         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9439                && toY == fromY && toX < fromX-1) {
9440         board[fromY][fromX] = EmptySquare;
9441         board[toY][toX] = king;
9442         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9443         board[fromY][BOARD_LEFT] = EmptySquare;
9444     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9445                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9446                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9447                ) {
9448         /* white pawn promotion */
9449         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9450         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) 
9451            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9452             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9453         board[fromY][fromX] = EmptySquare;
9454     } else if ((fromY >= BOARD_HEIGHT>>1)
9455                && (toX != fromX)
9456                && gameInfo.variant != VariantXiangqi
9457                && gameInfo.variant != VariantBerolina
9458                && (board[fromY][fromX] == WhitePawn)
9459                && (board[toY][toX] == EmptySquare)) {
9460         board[fromY][fromX] = EmptySquare;
9461         board[toY][toX] = WhitePawn;
9462         captured = board[toY - 1][toX];
9463         board[toY - 1][toX] = EmptySquare;
9464     } else if ((fromY == BOARD_HEIGHT-4)
9465                && (toX == fromX)
9466                && gameInfo.variant == VariantBerolina
9467                && (board[fromY][fromX] == WhitePawn)
9468                && (board[toY][toX] == EmptySquare)) {
9469         board[fromY][fromX] = EmptySquare;
9470         board[toY][toX] = WhitePawn;
9471         if(oldEP & EP_BEROLIN_A) {
9472                 captured = board[fromY][fromX-1];
9473                 board[fromY][fromX-1] = EmptySquare;
9474         }else{  captured = board[fromY][fromX+1];
9475                 board[fromY][fromX+1] = EmptySquare;
9476         }
9477     } else if (board[fromY][fromX] == king
9478         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9479                && toY == fromY && toX > fromX+1) {
9480         board[fromY][fromX] = EmptySquare;
9481         board[toY][toX] = king;
9482         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9483         board[fromY][BOARD_RGHT-1] = EmptySquare;
9484     } else if (board[fromY][fromX] == king
9485         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9486                && toY == fromY && toX < fromX-1) {
9487         board[fromY][fromX] = EmptySquare;
9488         board[toY][toX] = king;
9489         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9490         board[fromY][BOARD_LEFT] = EmptySquare;
9491     } else if (fromY == 7 && fromX == 3
9492                && board[fromY][fromX] == BlackKing
9493                && toY == 7 && toX == 5) {
9494         board[fromY][fromX] = EmptySquare;
9495         board[toY][toX] = BlackKing;
9496         board[fromY][7] = EmptySquare;
9497         board[toY][4] = BlackRook;
9498     } else if (fromY == 7 && fromX == 3
9499                && board[fromY][fromX] == BlackKing
9500                && toY == 7 && toX == 1) {
9501         board[fromY][fromX] = EmptySquare;
9502         board[toY][toX] = BlackKing;
9503         board[fromY][0] = EmptySquare;
9504         board[toY][2] = BlackRook;
9505     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9506                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9507                && toY < promoRank && promoChar
9508                ) {
9509         /* black pawn promotion */
9510         board[toY][toX] = CharToPiece(ToLower(promoChar));
9511         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) 
9512            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9513             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9514         board[fromY][fromX] = EmptySquare;
9515     } else if ((fromY < BOARD_HEIGHT>>1)
9516                && (toX != fromX)
9517                && gameInfo.variant != VariantXiangqi
9518                && gameInfo.variant != VariantBerolina
9519                && (board[fromY][fromX] == BlackPawn)
9520                && (board[toY][toX] == EmptySquare)) {
9521         board[fromY][fromX] = EmptySquare;
9522         board[toY][toX] = BlackPawn;
9523         captured = board[toY + 1][toX];
9524         board[toY + 1][toX] = EmptySquare;
9525     } else if ((fromY == 3)
9526                && (toX == fromX)
9527                && gameInfo.variant == VariantBerolina
9528                && (board[fromY][fromX] == BlackPawn)
9529                && (board[toY][toX] == EmptySquare)) {
9530         board[fromY][fromX] = EmptySquare;
9531         board[toY][toX] = BlackPawn;
9532         if(oldEP & EP_BEROLIN_A) {
9533                 captured = board[fromY][fromX-1];
9534                 board[fromY][fromX-1] = EmptySquare;
9535         }else{  captured = board[fromY][fromX+1];
9536                 board[fromY][fromX+1] = EmptySquare;
9537         }
9538     } else {
9539         board[toY][toX] = board[fromY][fromX];
9540         board[fromY][fromX] = EmptySquare;
9541     }
9542   }
9543
9544     if (gameInfo.holdingsWidth != 0) {
9545
9546       /* !!A lot more code needs to be written to support holdings  */
9547       /* [HGM] OK, so I have written it. Holdings are stored in the */
9548       /* penultimate board files, so they are automaticlly stored   */
9549       /* in the game history.                                       */
9550       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9551                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9552         /* Delete from holdings, by decreasing count */
9553         /* and erasing image if necessary            */
9554         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9555         if(p < (int) BlackPawn) { /* white drop */
9556              p -= (int)WhitePawn;
9557                  p = PieceToNumber((ChessSquare)p);
9558              if(p >= gameInfo.holdingsSize) p = 0;
9559              if(--board[p][BOARD_WIDTH-2] <= 0)
9560                   board[p][BOARD_WIDTH-1] = EmptySquare;
9561              if((int)board[p][BOARD_WIDTH-2] < 0)
9562                         board[p][BOARD_WIDTH-2] = 0;
9563         } else {                  /* black drop */
9564              p -= (int)BlackPawn;
9565                  p = PieceToNumber((ChessSquare)p);
9566              if(p >= gameInfo.holdingsSize) p = 0;
9567              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9568                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9569              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9570                         board[BOARD_HEIGHT-1-p][1] = 0;
9571         }
9572       }
9573       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9574           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9575         /* [HGM] holdings: Add to holdings, if holdings exist */
9576         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9577                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9578                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9579         }
9580         p = (int) captured;
9581         if (p >= (int) BlackPawn) {
9582           p -= (int)BlackPawn;
9583           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9584                   /* in Shogi restore piece to its original  first */
9585                   captured = (ChessSquare) (DEMOTED captured);
9586                   p = DEMOTED p;
9587           }
9588           p = PieceToNumber((ChessSquare)p);
9589           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9590           board[p][BOARD_WIDTH-2]++;
9591           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9592         } else {
9593           p -= (int)WhitePawn;
9594           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9595                   captured = (ChessSquare) (DEMOTED captured);
9596                   p = DEMOTED p;
9597           }
9598           p = PieceToNumber((ChessSquare)p);
9599           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9600           board[BOARD_HEIGHT-1-p][1]++;
9601           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9602         }
9603       }
9604     } else if (gameInfo.variant == VariantAtomic) {
9605       if (captured != EmptySquare) {
9606         int y, x;
9607         for (y = toY-1; y <= toY+1; y++) {
9608           for (x = toX-1; x <= toX+1; x++) {
9609             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9610                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9611               board[y][x] = EmptySquare;
9612             }
9613           }
9614         }
9615         board[toY][toX] = EmptySquare;
9616       }
9617     }
9618     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9619         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9620     } else
9621     if(promoChar == '+') {
9622         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9623         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9624     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9625         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9626         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9627            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9628         board[toY][toX] = newPiece;
9629     }
9630     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9631                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9632         // [HGM] superchess: take promotion piece out of holdings
9633         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9634         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9635             if(!--board[k][BOARD_WIDTH-2])
9636                 board[k][BOARD_WIDTH-1] = EmptySquare;
9637         } else {
9638             if(!--board[BOARD_HEIGHT-1-k][1])
9639                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9640         }
9641     }
9642
9643 }
9644
9645 /* Updates forwardMostMove */
9646 void
9647 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9648 {
9649 //    forwardMostMove++; // [HGM] bare: moved downstream
9650
9651     (void) CoordsToAlgebraic(boards[forwardMostMove],
9652                              PosFlags(forwardMostMove),
9653                              fromY, fromX, toY, toX, promoChar,
9654                              parseList[forwardMostMove]);
9655
9656     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9657         int timeLeft; static int lastLoadFlag=0; int king, piece;
9658         piece = boards[forwardMostMove][fromY][fromX];
9659         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9660         if(gameInfo.variant == VariantKnightmate)
9661             king += (int) WhiteUnicorn - (int) WhiteKing;
9662         if(forwardMostMove == 0) {
9663             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9664                 fprintf(serverMoves, "%s;", UserName());
9665             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9666                 fprintf(serverMoves, "%s;", second.tidy);
9667             fprintf(serverMoves, "%s;", first.tidy);
9668             if(gameMode == MachinePlaysWhite)
9669                 fprintf(serverMoves, "%s;", UserName());
9670             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9671                 fprintf(serverMoves, "%s;", second.tidy);
9672         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9673         lastLoadFlag = loadFlag;
9674         // print base move
9675         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9676         // print castling suffix
9677         if( toY == fromY && piece == king ) {
9678             if(toX-fromX > 1)
9679                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9680             if(fromX-toX >1)
9681                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9682         }
9683         // e.p. suffix
9684         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9685              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9686              boards[forwardMostMove][toY][toX] == EmptySquare
9687              && fromX != toX && fromY != toY)
9688                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9689         // promotion suffix
9690         if(promoChar != NULLCHAR) {
9691             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9692                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9693                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9694             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9695         }
9696         if(!loadFlag) {
9697                 char buf[MOVE_LEN*2], *p; int len;
9698             fprintf(serverMoves, "/%d/%d",
9699                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9700             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9701             else                      timeLeft = blackTimeRemaining/1000;
9702             fprintf(serverMoves, "/%d", timeLeft);
9703                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9704                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9705                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9706                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9707             fprintf(serverMoves, "/%s", buf);
9708         }
9709         fflush(serverMoves);
9710     }
9711
9712     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9713         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9714       return;
9715     }
9716     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9717     if (commentList[forwardMostMove+1] != NULL) {
9718         free(commentList[forwardMostMove+1]);
9719         commentList[forwardMostMove+1] = NULL;
9720     }
9721     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9722     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9723     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9724     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9725     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9726     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9727     adjustedClock = FALSE;
9728     gameInfo.result = GameUnfinished;
9729     if (gameInfo.resultDetails != NULL) {
9730         free(gameInfo.resultDetails);
9731         gameInfo.resultDetails = NULL;
9732     }
9733     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9734                               moveList[forwardMostMove - 1]);
9735     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9736       case MT_NONE:
9737       case MT_STALEMATE:
9738       default:
9739         break;
9740       case MT_CHECK:
9741         if(gameInfo.variant != VariantShogi)
9742             strcat(parseList[forwardMostMove - 1], "+");
9743         break;
9744       case MT_CHECKMATE:
9745       case MT_STAINMATE:
9746         strcat(parseList[forwardMostMove - 1], "#");
9747         break;
9748     }
9749
9750 }
9751
9752 /* Updates currentMove if not pausing */
9753 void
9754 ShowMove (int fromX, int fromY, int toX, int toY)
9755 {
9756     int instant = (gameMode == PlayFromGameFile) ?
9757         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9758     if(appData.noGUI) return;
9759     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9760         if (!instant) {
9761             if (forwardMostMove == currentMove + 1) {
9762                 AnimateMove(boards[forwardMostMove - 1],
9763                             fromX, fromY, toX, toY);
9764             }
9765         }
9766         currentMove = forwardMostMove;
9767     }
9768
9769     if (instant) return;
9770
9771     DisplayMove(currentMove - 1);
9772     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9773             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9774                 SetHighlights(fromX, fromY, toX, toY);
9775             }
9776     }
9777     DrawPosition(FALSE, boards[currentMove]);
9778     DisplayBothClocks();
9779     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9780 }
9781
9782 void
9783 SendEgtPath (ChessProgramState *cps)
9784 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9785         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9786
9787         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9788
9789         while(*p) {
9790             char c, *q = name+1, *r, *s;
9791
9792             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9793             while(*p && *p != ',') *q++ = *p++;
9794             *q++ = ':'; *q = 0;
9795             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9796                 strcmp(name, ",nalimov:") == 0 ) {
9797                 // take nalimov path from the menu-changeable option first, if it is defined
9798               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9799                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9800             } else
9801             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9802                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9803                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9804                 s = r = StrStr(s, ":") + 1; // beginning of path info
9805                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9806                 c = *r; *r = 0;             // temporarily null-terminate path info
9807                     *--q = 0;               // strip of trailig ':' from name
9808                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9809                 *r = c;
9810                 SendToProgram(buf,cps);     // send egtbpath command for this format
9811             }
9812             if(*p == ',') p++; // read away comma to position for next format name
9813         }
9814 }
9815
9816 void
9817 InitChessProgram (ChessProgramState *cps, int setup)
9818 /* setup needed to setup FRC opening position */
9819 {
9820     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9821     if (appData.noChessProgram) return;
9822     hintRequested = FALSE;
9823     bookRequested = FALSE;
9824
9825     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9826     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9827     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9828     if(cps->memSize) { /* [HGM] memory */
9829       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9830         SendToProgram(buf, cps);
9831     }
9832     SendEgtPath(cps); /* [HGM] EGT */
9833     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9834       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9835         SendToProgram(buf, cps);
9836     }
9837
9838     SendToProgram(cps->initString, cps);
9839     if (gameInfo.variant != VariantNormal &&
9840         gameInfo.variant != VariantLoadable
9841         /* [HGM] also send variant if board size non-standard */
9842         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9843                                             ) {
9844       char *v = VariantName(gameInfo.variant);
9845       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9846         /* [HGM] in protocol 1 we have to assume all variants valid */
9847         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9848         DisplayFatalError(buf, 0, 1);
9849         return;
9850       }
9851
9852       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9853       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9854       if( gameInfo.variant == VariantXiangqi )
9855            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9856       if( gameInfo.variant == VariantShogi )
9857            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9858       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9859            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9860       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9861           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9862            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9863       if( gameInfo.variant == VariantCourier )
9864            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9865       if( gameInfo.variant == VariantSuper )
9866            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9867       if( gameInfo.variant == VariantGreat )
9868            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9869       if( gameInfo.variant == VariantSChess )
9870            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9871       if( gameInfo.variant == VariantGrand )
9872            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9873
9874       if(overruled) {
9875         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9876                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9877            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9878            if(StrStr(cps->variants, b) == NULL) {
9879                // specific sized variant not known, check if general sizing allowed
9880                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9881                    if(StrStr(cps->variants, "boardsize") == NULL) {
9882                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9883                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9884                        DisplayFatalError(buf, 0, 1);
9885                        return;
9886                    }
9887                    /* [HGM] here we really should compare with the maximum supported board size */
9888                }
9889            }
9890       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9891       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9892       SendToProgram(buf, cps);
9893     }
9894     currentlyInitializedVariant = gameInfo.variant;
9895
9896     /* [HGM] send opening position in FRC to first engine */
9897     if(setup) {
9898           SendToProgram("force\n", cps);
9899           SendBoard(cps, 0);
9900           /* engine is now in force mode! Set flag to wake it up after first move. */
9901           setboardSpoiledMachineBlack = 1;
9902     }
9903
9904     if (cps->sendICS) {
9905       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9906       SendToProgram(buf, cps);
9907     }
9908     cps->maybeThinking = FALSE;
9909     cps->offeredDraw = 0;
9910     if (!appData.icsActive) {
9911         SendTimeControl(cps, movesPerSession, timeControl,
9912                         timeIncrement, appData.searchDepth,
9913                         searchTime);
9914     }
9915     if (appData.showThinking
9916         // [HGM] thinking: four options require thinking output to be sent
9917         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9918                                 ) {
9919         SendToProgram("post\n", cps);
9920     }
9921     SendToProgram("hard\n", cps);
9922     if (!appData.ponderNextMove) {
9923         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9924            it without being sure what state we are in first.  "hard"
9925            is not a toggle, so that one is OK.
9926          */
9927         SendToProgram("easy\n", cps);
9928     }
9929     if (cps->usePing) {
9930       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9931       SendToProgram(buf, cps);
9932     }
9933     cps->initDone = TRUE;
9934     ClearEngineOutputPane(cps == &second);
9935 }
9936
9937
9938 void
9939 StartChessProgram (ChessProgramState *cps)
9940 {
9941     char buf[MSG_SIZ];
9942     int err;
9943
9944     if (appData.noChessProgram) return;
9945     cps->initDone = FALSE;
9946
9947     if (strcmp(cps->host, "localhost") == 0) {
9948         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9949     } else if (*appData.remoteShell == NULLCHAR) {
9950         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9951     } else {
9952         if (*appData.remoteUser == NULLCHAR) {
9953           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9954                     cps->program);
9955         } else {
9956           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9957                     cps->host, appData.remoteUser, cps->program);
9958         }
9959         err = StartChildProcess(buf, "", &cps->pr);
9960     }
9961
9962     if (err != 0) {
9963       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9964         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9965         if(cps != &first) return;
9966         appData.noChessProgram = TRUE;
9967         ThawUI();
9968         SetNCPMode();
9969 //      DisplayFatalError(buf, err, 1);
9970 //      cps->pr = NoProc;
9971 //      cps->isr = NULL;
9972         return;
9973     }
9974
9975     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9976     if (cps->protocolVersion > 1) {
9977       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9978       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9979       cps->comboCnt = 0;  //                and values of combo boxes
9980       SendToProgram(buf, cps);
9981     } else {
9982       SendToProgram("xboard\n", cps);
9983     }
9984 }
9985
9986 void
9987 TwoMachinesEventIfReady P((void))
9988 {
9989   static int curMess = 0;
9990   if (first.lastPing != first.lastPong) {
9991     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9992     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9993     return;
9994   }
9995   if (second.lastPing != second.lastPong) {
9996     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9997     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9998     return;
9999   }
10000   DisplayMessage("", ""); curMess = 0;
10001   ThawUI();
10002   TwoMachinesEvent();
10003 }
10004
10005 char *
10006 MakeName (char *template)
10007 {
10008     time_t clock;
10009     struct tm *tm;
10010     static char buf[MSG_SIZ];
10011     char *p = buf;
10012     int i;
10013
10014     clock = time((time_t *)NULL);
10015     tm = localtime(&clock);
10016
10017     while(*p++ = *template++) if(p[-1] == '%') {
10018         switch(*template++) {
10019           case 0:   *p = 0; return buf;
10020           case 'Y': i = tm->tm_year+1900; break;
10021           case 'y': i = tm->tm_year-100; break;
10022           case 'M': i = tm->tm_mon+1; break;
10023           case 'd': i = tm->tm_mday; break;
10024           case 'h': i = tm->tm_hour; break;
10025           case 'm': i = tm->tm_min; break;
10026           case 's': i = tm->tm_sec; break;
10027           default:  i = 0;
10028         }
10029         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10030     }
10031     return buf;
10032 }
10033
10034 int
10035 CountPlayers (char *p)
10036 {
10037     int n = 0;
10038     while(p = strchr(p, '\n')) p++, n++; // count participants
10039     return n;
10040 }
10041
10042 FILE *
10043 WriteTourneyFile (char *results, FILE *f)
10044 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10045     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10046     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10047         // create a file with tournament description
10048         fprintf(f, "-participants {%s}\n", appData.participants);
10049         fprintf(f, "-seedBase %d\n", appData.seedBase);
10050         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10051         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10052         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10053         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10054         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10055         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10056         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10057         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10058         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10059         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10060         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10061         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10062         if(searchTime > 0)
10063                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10064         else {
10065                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10066                 fprintf(f, "-tc %s\n", appData.timeControl);
10067                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10068         }
10069         fprintf(f, "-results \"%s\"\n", results);
10070     }
10071     return f;
10072 }
10073
10074 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10075
10076 void
10077 Substitute (char *participants, int expunge)
10078 {
10079     int i, changed, changes=0, nPlayers=0;
10080     char *p, *q, *r, buf[MSG_SIZ];
10081     if(participants == NULL) return;
10082     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10083     r = p = participants; q = appData.participants;
10084     while(*p && *p == *q) {
10085         if(*p == '\n') r = p+1, nPlayers++;
10086         p++; q++;
10087     }
10088     if(*p) { // difference
10089         while(*p && *p++ != '\n');
10090         while(*q && *q++ != '\n');
10091       changed = nPlayers;
10092         changes = 1 + (strcmp(p, q) != 0);
10093     }
10094     if(changes == 1) { // a single engine mnemonic was changed
10095         q = r; while(*q) nPlayers += (*q++ == '\n');
10096         p = buf; while(*r && (*p = *r++) != '\n') p++;
10097         *p = NULLCHAR;
10098         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10099         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10100         if(mnemonic[i]) { // The substitute is valid
10101             FILE *f;
10102             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10103                 flock(fileno(f), LOCK_EX);
10104                 ParseArgsFromFile(f);
10105                 fseek(f, 0, SEEK_SET);
10106                 FREE(appData.participants); appData.participants = participants;
10107                 if(expunge) { // erase results of replaced engine
10108                     int len = strlen(appData.results), w, b, dummy;
10109                     for(i=0; i<len; i++) {
10110                         Pairing(i, nPlayers, &w, &b, &dummy);
10111                         if((w == changed || b == changed) && appData.results[i] == '*') {
10112                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10113                             fclose(f);
10114                             return;
10115                         }
10116                     }
10117                     for(i=0; i<len; i++) {
10118                         Pairing(i, nPlayers, &w, &b, &dummy);
10119                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10120                     }
10121                 }
10122                 WriteTourneyFile(appData.results, f);
10123                 fclose(f); // release lock
10124                 return;
10125             }
10126         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10127     }
10128     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10129     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10130     free(participants);
10131     return;
10132 }
10133
10134 int
10135 CheckPlayers (char *participants)
10136 {
10137         int i;
10138         char buf[MSG_SIZ], *p;
10139         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10140         while(p = strchr(participants, '\n')) {
10141             *p = NULLCHAR;
10142             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10143             if(!mnemonic[i]) {
10144                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10145                 *p = '\n';
10146                 DisplayError(buf, 0);
10147                 return 1;
10148             }
10149             *p = '\n';
10150             participants = p + 1;
10151         }
10152         return 0;
10153 }
10154
10155 int
10156 CreateTourney (char *name)
10157 {
10158         FILE *f;
10159         if(matchMode && strcmp(name, appData.tourneyFile)) {
10160              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10161         }
10162         if(name[0] == NULLCHAR) {
10163             if(appData.participants[0])
10164                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10165             return 0;
10166         }
10167         f = fopen(name, "r");
10168         if(f) { // file exists
10169             ASSIGN(appData.tourneyFile, name);
10170             ParseArgsFromFile(f); // parse it
10171         } else {
10172             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10173             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10174                 DisplayError(_("Not enough participants"), 0);
10175                 return 0;
10176             }
10177             if(CheckPlayers(appData.participants)) return 0;
10178             ASSIGN(appData.tourneyFile, name);
10179             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10180             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10181         }
10182         fclose(f);
10183         appData.noChessProgram = FALSE;
10184         appData.clockMode = TRUE;
10185         SetGNUMode();
10186         return 1;
10187 }
10188
10189 int
10190 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10191 {
10192     char buf[MSG_SIZ], *p, *q;
10193     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10194     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10195     skip = !all && group[0]; // if group requested, we start in skip mode
10196     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10197         p = names; q = buf; header = 0;
10198         while(*p && *p != '\n') *q++ = *p++;
10199         *q = 0;
10200         if(*p == '\n') p++;
10201         if(buf[0] == '#') {
10202             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10203             depth++; // we must be entering a new group
10204             if(all) continue; // suppress printing group headers when complete list requested
10205             header = 1;
10206             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10207         }
10208         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10209         if(engineList[i]) free(engineList[i]);
10210         engineList[i] = strdup(buf);
10211         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10212         if(engineMnemonic[i]) free(engineMnemonic[i]);
10213         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10214             strcat(buf, " (");
10215             sscanf(q + 8, "%s", buf + strlen(buf));
10216             strcat(buf, ")");
10217         }
10218         engineMnemonic[i] = strdup(buf);
10219         i++;
10220     }
10221     engineList[i] = engineMnemonic[i] = NULL;
10222     return i;
10223 }
10224
10225 // following implemented as macro to avoid type limitations
10226 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10227
10228 void
10229 SwapEngines (int n)
10230 {   // swap settings for first engine and other engine (so far only some selected options)
10231     int h;
10232     char *p;
10233     if(n == 0) return;
10234     SWAP(directory, p)
10235     SWAP(chessProgram, p)
10236     SWAP(isUCI, h)
10237     SWAP(hasOwnBookUCI, h)
10238     SWAP(protocolVersion, h)
10239     SWAP(reuse, h)
10240     SWAP(scoreIsAbsolute, h)
10241     SWAP(timeOdds, h)
10242     SWAP(logo, p)
10243     SWAP(pgnName, p)
10244     SWAP(pvSAN, h)
10245     SWAP(engOptions, p)
10246     SWAP(engInitString, p)
10247     SWAP(computerString, p)
10248     SWAP(features, p)
10249     SWAP(fenOverride, p)
10250     SWAP(NPS, h)
10251     SWAP(accumulateTC, h)
10252     SWAP(host, p)
10253 }
10254
10255 int
10256 GetEngineLine (char *s, int n)
10257 {
10258     int i;
10259     char buf[MSG_SIZ];
10260     extern char *icsNames;
10261     if(!s || !*s) return 0;
10262     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10263     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10264     if(!mnemonic[i]) return 0;
10265     if(n == 11) return 1; // just testing if there was a match
10266     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10267     if(n == 1) SwapEngines(n);
10268     ParseArgsFromString(buf);
10269     if(n == 1) SwapEngines(n);
10270     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10271         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10272         ParseArgsFromString(buf);
10273     }
10274     return 1;
10275 }
10276
10277 int
10278 SetPlayer (int player, char *p)
10279 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10280     int i;
10281     char buf[MSG_SIZ], *engineName;
10282     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10283     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10284     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10285     if(mnemonic[i]) {
10286         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10287         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10288         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10289         ParseArgsFromString(buf);
10290     }
10291     free(engineName);
10292     return i;
10293 }
10294
10295 char *recentEngines;
10296
10297 void
10298 RecentEngineEvent (int nr)
10299 {
10300     int n;
10301 //    SwapEngines(1); // bump first to second
10302 //    ReplaceEngine(&second, 1); // and load it there
10303     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10304     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10305     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10306         ReplaceEngine(&first, 0);
10307         FloatToFront(&appData.recentEngineList, command[n]);
10308     }
10309 }
10310
10311 int
10312 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10313 {   // determine players from game number
10314     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10315
10316     if(appData.tourneyType == 0) {
10317         roundsPerCycle = (nPlayers - 1) | 1;
10318         pairingsPerRound = nPlayers / 2;
10319     } else if(appData.tourneyType > 0) {
10320         roundsPerCycle = nPlayers - appData.tourneyType;
10321         pairingsPerRound = appData.tourneyType;
10322     }
10323     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10324     gamesPerCycle = gamesPerRound * roundsPerCycle;
10325     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10326     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10327     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10328     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10329     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10330     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10331
10332     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10333     if(appData.roundSync) *syncInterval = gamesPerRound;
10334
10335     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10336
10337     if(appData.tourneyType == 0) {
10338         if(curPairing == (nPlayers-1)/2 ) {
10339             *whitePlayer = curRound;
10340             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10341         } else {
10342             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10343             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10344             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10345             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10346         }
10347     } else if(appData.tourneyType > 1) {
10348         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10349         *whitePlayer = curRound + appData.tourneyType;
10350     } else if(appData.tourneyType > 0) {
10351         *whitePlayer = curPairing;
10352         *blackPlayer = curRound + appData.tourneyType;
10353     }
10354
10355     // take care of white/black alternation per round. 
10356     // For cycles and games this is already taken care of by default, derived from matchGame!
10357     return curRound & 1;
10358 }
10359
10360 int
10361 NextTourneyGame (int nr, int *swapColors)
10362 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10363     char *p, *q;
10364     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10365     FILE *tf;
10366     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10367     tf = fopen(appData.tourneyFile, "r");
10368     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10369     ParseArgsFromFile(tf); fclose(tf);
10370     InitTimeControls(); // TC might be altered from tourney file
10371
10372     nPlayers = CountPlayers(appData.participants); // count participants
10373     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10374     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10375
10376     if(syncInterval) {
10377         p = q = appData.results;
10378         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10379         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10380             DisplayMessage(_("Waiting for other game(s)"),"");
10381             waitingForGame = TRUE;
10382             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10383             return 0;
10384         }
10385         waitingForGame = FALSE;
10386     }
10387
10388     if(appData.tourneyType < 0) {
10389         if(nr>=0 && !pairingReceived) {
10390             char buf[1<<16];
10391             if(pairing.pr == NoProc) {
10392                 if(!appData.pairingEngine[0]) {
10393                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10394                     return 0;
10395                 }
10396                 StartChessProgram(&pairing); // starts the pairing engine
10397             }
10398             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10399             SendToProgram(buf, &pairing);
10400             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10401             SendToProgram(buf, &pairing);
10402             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10403         }
10404         pairingReceived = 0;                              // ... so we continue here 
10405         *swapColors = 0;
10406         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10407         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10408         matchGame = 1; roundNr = nr / syncInterval + 1;
10409     }
10410
10411     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10412
10413     // redefine engines, engine dir, etc.
10414     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10415     if(first.pr == NoProc) {
10416       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10417       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10418     }
10419     if(second.pr == NoProc) {
10420       SwapEngines(1);
10421       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10422       SwapEngines(1);         // and make that valid for second engine by swapping
10423       InitEngine(&second, 1);
10424     }
10425     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10426     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10427     return 1;
10428 }
10429
10430 void
10431 NextMatchGame ()
10432 {   // performs game initialization that does not invoke engines, and then tries to start the game
10433     int res, firstWhite, swapColors = 0;
10434     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10435     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
10436         char buf[MSG_SIZ];
10437         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10438         if(strcmp(buf, currentDebugFile)) { // name has changed
10439             FILE *f = fopen(buf, "w");
10440             if(f) { // if opening the new file failed, just keep using the old one
10441                 ASSIGN(currentDebugFile, buf);
10442                 fclose(debugFP);
10443                 debugFP = f;
10444             }
10445             if(appData.serverFileName) {
10446                 if(serverFP) fclose(serverFP);
10447                 serverFP = fopen(appData.serverFileName, "w");
10448                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10449                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10450             }
10451         }
10452     }
10453     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10454     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10455     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10456     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10457     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10458     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10459     Reset(FALSE, first.pr != NoProc);
10460     res = LoadGameOrPosition(matchGame); // setup game
10461     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10462     if(!res) return; // abort when bad game/pos file
10463     TwoMachinesEvent();
10464 }
10465
10466 void
10467 UserAdjudicationEvent (int result)
10468 {
10469     ChessMove gameResult = GameIsDrawn;
10470
10471     if( result > 0 ) {
10472         gameResult = WhiteWins;
10473     }
10474     else if( result < 0 ) {
10475         gameResult = BlackWins;
10476     }
10477
10478     if( gameMode == TwoMachinesPlay ) {
10479         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10480     }
10481 }
10482
10483
10484 // [HGM] save: calculate checksum of game to make games easily identifiable
10485 int
10486 StringCheckSum (char *s)
10487 {
10488         int i = 0;
10489         if(s==NULL) return 0;
10490         while(*s) i = i*259 + *s++;
10491         return i;
10492 }
10493
10494 int
10495 GameCheckSum ()
10496 {
10497         int i, sum=0;
10498         for(i=backwardMostMove; i<forwardMostMove; i++) {
10499                 sum += pvInfoList[i].depth;
10500                 sum += StringCheckSum(parseList[i]);
10501                 sum += StringCheckSum(commentList[i]);
10502                 sum *= 261;
10503         }
10504         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10505         return sum + StringCheckSum(commentList[i]);
10506 } // end of save patch
10507
10508 void
10509 GameEnds (ChessMove result, char *resultDetails, int whosays)
10510 {
10511     GameMode nextGameMode;
10512     int isIcsGame;
10513     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10514
10515     if(endingGame) return; /* [HGM] crash: forbid recursion */
10516     endingGame = 1;
10517     if(twoBoards) { // [HGM] dual: switch back to one board
10518         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10519         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10520     }
10521     if (appData.debugMode) {
10522       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10523               result, resultDetails ? resultDetails : "(null)", whosays);
10524     }
10525
10526     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10527
10528     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10529
10530     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10531         /* If we are playing on ICS, the server decides when the
10532            game is over, but the engine can offer to draw, claim
10533            a draw, or resign.
10534          */
10535 #if ZIPPY
10536         if (appData.zippyPlay && first.initDone) {
10537             if (result == GameIsDrawn) {
10538                 /* In case draw still needs to be claimed */
10539                 SendToICS(ics_prefix);
10540                 SendToICS("draw\n");
10541             } else if (StrCaseStr(resultDetails, "resign")) {
10542                 SendToICS(ics_prefix);
10543                 SendToICS("resign\n");
10544             }
10545         }
10546 #endif
10547         endingGame = 0; /* [HGM] crash */
10548         return;
10549     }
10550
10551     /* If we're loading the game from a file, stop */
10552     if (whosays == GE_FILE) {
10553       (void) StopLoadGameTimer();
10554       gameFileFP = NULL;
10555     }
10556
10557     /* Cancel draw offers */
10558     first.offeredDraw = second.offeredDraw = 0;
10559
10560     /* If this is an ICS game, only ICS can really say it's done;
10561        if not, anyone can. */
10562     isIcsGame = (gameMode == IcsPlayingWhite ||
10563                  gameMode == IcsPlayingBlack ||
10564                  gameMode == IcsObserving    ||
10565                  gameMode == IcsExamining);
10566
10567     if (!isIcsGame || whosays == GE_ICS) {
10568         /* OK -- not an ICS game, or ICS said it was done */
10569         StopClocks();
10570         if (!isIcsGame && !appData.noChessProgram)
10571           SetUserThinkingEnables();
10572
10573         /* [HGM] if a machine claims the game end we verify this claim */
10574         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10575             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10576                 char claimer;
10577                 ChessMove trueResult = (ChessMove) -1;
10578
10579                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10580                                             first.twoMachinesColor[0] :
10581                                             second.twoMachinesColor[0] ;
10582
10583                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10584                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10585                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10586                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10587                 } else
10588                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10589                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10590                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10591                 } else
10592                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10593                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10594                 }
10595
10596                 // now verify win claims, but not in drop games, as we don't understand those yet
10597                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10598                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10599                     (result == WhiteWins && claimer == 'w' ||
10600                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10601                       if (appData.debugMode) {
10602                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10603                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10604                       }
10605                       if(result != trueResult) {
10606                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10607                               result = claimer == 'w' ? BlackWins : WhiteWins;
10608                               resultDetails = buf;
10609                       }
10610                 } else
10611                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10612                     && (forwardMostMove <= backwardMostMove ||
10613                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10614                         (claimer=='b')==(forwardMostMove&1))
10615                                                                                   ) {
10616                       /* [HGM] verify: draws that were not flagged are false claims */
10617                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10618                       result = claimer == 'w' ? BlackWins : WhiteWins;
10619                       resultDetails = buf;
10620                 }
10621                 /* (Claiming a loss is accepted no questions asked!) */
10622             }
10623             /* [HGM] bare: don't allow bare King to win */
10624             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10625                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10626                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10627                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10628                && result != GameIsDrawn)
10629             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10630                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10631                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10632                         if(p >= 0 && p <= (int)WhiteKing) k++;
10633                 }
10634                 if (appData.debugMode) {
10635                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10636                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10637                 }
10638                 if(k <= 1) {
10639                         result = GameIsDrawn;
10640                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10641                         resultDetails = buf;
10642                 }
10643             }
10644         }
10645
10646
10647         if(serverMoves != NULL && !loadFlag) { char c = '=';
10648             if(result==WhiteWins) c = '+';
10649             if(result==BlackWins) c = '-';
10650             if(resultDetails != NULL)
10651                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10652         }
10653         if (resultDetails != NULL) {
10654             gameInfo.result = result;
10655             gameInfo.resultDetails = StrSave(resultDetails);
10656
10657             /* display last move only if game was not loaded from file */
10658             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10659                 DisplayMove(currentMove - 1);
10660
10661             if (forwardMostMove != 0) {
10662                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10663                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10664                                                                 ) {
10665                     if (*appData.saveGameFile != NULLCHAR) {
10666                         SaveGameToFile(appData.saveGameFile, TRUE);
10667                     } else if (appData.autoSaveGames) {
10668                         AutoSaveGame();
10669                     }
10670                     if (*appData.savePositionFile != NULLCHAR) {
10671                         SavePositionToFile(appData.savePositionFile);
10672                     }
10673                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10674                 }
10675             }
10676
10677             /* Tell program how game ended in case it is learning */
10678             /* [HGM] Moved this to after saving the PGN, just in case */
10679             /* engine died and we got here through time loss. In that */
10680             /* case we will get a fatal error writing the pipe, which */
10681             /* would otherwise lose us the PGN.                       */
10682             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10683             /* output during GameEnds should never be fatal anymore   */
10684             if (gameMode == MachinePlaysWhite ||
10685                 gameMode == MachinePlaysBlack ||
10686                 gameMode == TwoMachinesPlay ||
10687                 gameMode == IcsPlayingWhite ||
10688                 gameMode == IcsPlayingBlack ||
10689                 gameMode == BeginningOfGame) {
10690                 char buf[MSG_SIZ];
10691                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10692                         resultDetails);
10693                 if (first.pr != NoProc) {
10694                     SendToProgram(buf, &first);
10695                 }
10696                 if (second.pr != NoProc &&
10697                     gameMode == TwoMachinesPlay) {
10698                     SendToProgram(buf, &second);
10699                 }
10700             }
10701         }
10702
10703         if (appData.icsActive) {
10704             if (appData.quietPlay &&
10705                 (gameMode == IcsPlayingWhite ||
10706                  gameMode == IcsPlayingBlack)) {
10707                 SendToICS(ics_prefix);
10708                 SendToICS("set shout 1\n");
10709             }
10710             nextGameMode = IcsIdle;
10711             ics_user_moved = FALSE;
10712             /* clean up premove.  It's ugly when the game has ended and the
10713              * premove highlights are still on the board.
10714              */
10715             if (gotPremove) {
10716               gotPremove = FALSE;
10717               ClearPremoveHighlights();
10718               DrawPosition(FALSE, boards[currentMove]);
10719             }
10720             if (whosays == GE_ICS) {
10721                 switch (result) {
10722                 case WhiteWins:
10723                     if (gameMode == IcsPlayingWhite)
10724                         PlayIcsWinSound();
10725                     else if(gameMode == IcsPlayingBlack)
10726                         PlayIcsLossSound();
10727                     break;
10728                 case BlackWins:
10729                     if (gameMode == IcsPlayingBlack)
10730                         PlayIcsWinSound();
10731                     else if(gameMode == IcsPlayingWhite)
10732                         PlayIcsLossSound();
10733                     break;
10734                 case GameIsDrawn:
10735                     PlayIcsDrawSound();
10736                     break;
10737                 default:
10738                     PlayIcsUnfinishedSound();
10739                 }
10740             }
10741         } else if (gameMode == EditGame ||
10742                    gameMode == PlayFromGameFile ||
10743                    gameMode == AnalyzeMode ||
10744                    gameMode == AnalyzeFile) {
10745             nextGameMode = gameMode;
10746         } else {
10747             nextGameMode = EndOfGame;
10748         }
10749         pausing = FALSE;
10750         ModeHighlight();
10751     } else {
10752         nextGameMode = gameMode;
10753     }
10754
10755     if (appData.noChessProgram) {
10756         gameMode = nextGameMode;
10757         ModeHighlight();
10758         endingGame = 0; /* [HGM] crash */
10759         return;
10760     }
10761
10762     if (first.reuse) {
10763         /* Put first chess program into idle state */
10764         if (first.pr != NoProc &&
10765             (gameMode == MachinePlaysWhite ||
10766              gameMode == MachinePlaysBlack ||
10767              gameMode == TwoMachinesPlay ||
10768              gameMode == IcsPlayingWhite ||
10769              gameMode == IcsPlayingBlack ||
10770              gameMode == BeginningOfGame)) {
10771             SendToProgram("force\n", &first);
10772             if (first.usePing) {
10773               char buf[MSG_SIZ];
10774               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10775               SendToProgram(buf, &first);
10776             }
10777         }
10778     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10779         /* Kill off first chess program */
10780         if (first.isr != NULL)
10781           RemoveInputSource(first.isr);
10782         first.isr = NULL;
10783
10784         if (first.pr != NoProc) {
10785             ExitAnalyzeMode();
10786             DoSleep( appData.delayBeforeQuit );
10787             SendToProgram("quit\n", &first);
10788             DoSleep( appData.delayAfterQuit );
10789             DestroyChildProcess(first.pr, first.useSigterm);
10790         }
10791         first.pr = NoProc;
10792     }
10793     if (second.reuse) {
10794         /* Put second chess program into idle state */
10795         if (second.pr != NoProc &&
10796             gameMode == TwoMachinesPlay) {
10797             SendToProgram("force\n", &second);
10798             if (second.usePing) {
10799               char buf[MSG_SIZ];
10800               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10801               SendToProgram(buf, &second);
10802             }
10803         }
10804     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10805         /* Kill off second chess program */
10806         if (second.isr != NULL)
10807           RemoveInputSource(second.isr);
10808         second.isr = NULL;
10809
10810         if (second.pr != NoProc) {
10811             DoSleep( appData.delayBeforeQuit );
10812             SendToProgram("quit\n", &second);
10813             DoSleep( appData.delayAfterQuit );
10814             DestroyChildProcess(second.pr, second.useSigterm);
10815         }
10816         second.pr = NoProc;
10817     }
10818
10819     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10820         char resChar = '=';
10821         switch (result) {
10822         case WhiteWins:
10823           resChar = '+';
10824           if (first.twoMachinesColor[0] == 'w') {
10825             first.matchWins++;
10826           } else {
10827             second.matchWins++;
10828           }
10829           break;
10830         case BlackWins:
10831           resChar = '-';
10832           if (first.twoMachinesColor[0] == 'b') {
10833             first.matchWins++;
10834           } else {
10835             second.matchWins++;
10836           }
10837           break;
10838         case GameUnfinished:
10839           resChar = ' ';
10840         default:
10841           break;
10842         }
10843
10844         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10845         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10846             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10847             ReserveGame(nextGame, resChar); // sets nextGame
10848             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10849             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10850         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10851
10852         if (nextGame <= appData.matchGames && !abortMatch) {
10853             gameMode = nextGameMode;
10854             matchGame = nextGame; // this will be overruled in tourney mode!
10855             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10856             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10857             endingGame = 0; /* [HGM] crash */
10858             return;
10859         } else {
10860             gameMode = nextGameMode;
10861             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10862                      first.tidy, second.tidy,
10863                      first.matchWins, second.matchWins,
10864                      appData.matchGames - (first.matchWins + second.matchWins));
10865             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10866             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10867             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10868             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10869                 first.twoMachinesColor = "black\n";
10870                 second.twoMachinesColor = "white\n";
10871             } else {
10872                 first.twoMachinesColor = "white\n";
10873                 second.twoMachinesColor = "black\n";
10874             }
10875         }
10876     }
10877     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10878         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10879       ExitAnalyzeMode();
10880     gameMode = nextGameMode;
10881     ModeHighlight();
10882     endingGame = 0;  /* [HGM] crash */
10883     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10884         if(matchMode == TRUE) { // match through command line: exit with or without popup
10885             if(ranking) {
10886                 ToNrEvent(forwardMostMove);
10887                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10888                 else ExitEvent(0);
10889             } else DisplayFatalError(buf, 0, 0);
10890         } else { // match through menu; just stop, with or without popup
10891             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10892             ModeHighlight();
10893             if(ranking){
10894                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10895             } else DisplayNote(buf);
10896       }
10897       if(ranking) free(ranking);
10898     }
10899 }
10900
10901 /* Assumes program was just initialized (initString sent).
10902    Leaves program in force mode. */
10903 void
10904 FeedMovesToProgram (ChessProgramState *cps, int upto)
10905 {
10906     int i;
10907
10908     if (appData.debugMode)
10909       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10910               startedFromSetupPosition ? "position and " : "",
10911               backwardMostMove, upto, cps->which);
10912     if(currentlyInitializedVariant != gameInfo.variant) {
10913       char buf[MSG_SIZ];
10914         // [HGM] variantswitch: make engine aware of new variant
10915         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10916                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10917         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10918         SendToProgram(buf, cps);
10919         currentlyInitializedVariant = gameInfo.variant;
10920     }
10921     SendToProgram("force\n", cps);
10922     if (startedFromSetupPosition) {
10923         SendBoard(cps, backwardMostMove);
10924     if (appData.debugMode) {
10925         fprintf(debugFP, "feedMoves\n");
10926     }
10927     }
10928     for (i = backwardMostMove; i < upto; i++) {
10929         SendMoveToProgram(i, cps);
10930     }
10931 }
10932
10933
10934 int
10935 ResurrectChessProgram ()
10936 {
10937      /* The chess program may have exited.
10938         If so, restart it and feed it all the moves made so far. */
10939     static int doInit = 0;
10940
10941     if (appData.noChessProgram) return 1;
10942
10943     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10944         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10945         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10946         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10947     } else {
10948         if (first.pr != NoProc) return 1;
10949         StartChessProgram(&first);
10950     }
10951     InitChessProgram(&first, FALSE);
10952     FeedMovesToProgram(&first, currentMove);
10953
10954     if (!first.sendTime) {
10955         /* can't tell gnuchess what its clock should read,
10956            so we bow to its notion. */
10957         ResetClocks();
10958         timeRemaining[0][currentMove] = whiteTimeRemaining;
10959         timeRemaining[1][currentMove] = blackTimeRemaining;
10960     }
10961
10962     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10963                 appData.icsEngineAnalyze) && first.analysisSupport) {
10964       SendToProgram("analyze\n", &first);
10965       first.analyzing = TRUE;
10966     }
10967     return 1;
10968 }
10969
10970 /*
10971  * Button procedures
10972  */
10973 void
10974 Reset (int redraw, int init)
10975 {
10976     int i;
10977
10978     if (appData.debugMode) {
10979         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10980                 redraw, init, gameMode);
10981     }
10982     CleanupTail(); // [HGM] vari: delete any stored variations
10983     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10984     pausing = pauseExamInvalid = FALSE;
10985     startedFromSetupPosition = blackPlaysFirst = FALSE;
10986     firstMove = TRUE;
10987     whiteFlag = blackFlag = FALSE;
10988     userOfferedDraw = FALSE;
10989     hintRequested = bookRequested = FALSE;
10990     first.maybeThinking = FALSE;
10991     second.maybeThinking = FALSE;
10992     first.bookSuspend = FALSE; // [HGM] book
10993     second.bookSuspend = FALSE;
10994     thinkOutput[0] = NULLCHAR;
10995     lastHint[0] = NULLCHAR;
10996     ClearGameInfo(&gameInfo);
10997     gameInfo.variant = StringToVariant(appData.variant);
10998     ics_user_moved = ics_clock_paused = FALSE;
10999     ics_getting_history = H_FALSE;
11000     ics_gamenum = -1;
11001     white_holding[0] = black_holding[0] = NULLCHAR;
11002     ClearProgramStats();
11003     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11004
11005     ResetFrontEnd();
11006     ClearHighlights();
11007     flipView = appData.flipView;
11008     ClearPremoveHighlights();
11009     gotPremove = FALSE;
11010     alarmSounded = FALSE;
11011
11012     GameEnds(EndOfFile, NULL, GE_PLAYER);
11013     if(appData.serverMovesName != NULL) {
11014         /* [HGM] prepare to make moves file for broadcasting */
11015         clock_t t = clock();
11016         if(serverMoves != NULL) fclose(serverMoves);
11017         serverMoves = fopen(appData.serverMovesName, "r");
11018         if(serverMoves != NULL) {
11019             fclose(serverMoves);
11020             /* delay 15 sec before overwriting, so all clients can see end */
11021             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11022         }
11023         serverMoves = fopen(appData.serverMovesName, "w");
11024     }
11025
11026     ExitAnalyzeMode();
11027     gameMode = BeginningOfGame;
11028     ModeHighlight();
11029     if(appData.icsActive) gameInfo.variant = VariantNormal;
11030     currentMove = forwardMostMove = backwardMostMove = 0;
11031     MarkTargetSquares(1);
11032     InitPosition(redraw);
11033     for (i = 0; i < MAX_MOVES; i++) {
11034         if (commentList[i] != NULL) {
11035             free(commentList[i]);
11036             commentList[i] = NULL;
11037         }
11038     }
11039     ResetClocks();
11040     timeRemaining[0][0] = whiteTimeRemaining;
11041     timeRemaining[1][0] = blackTimeRemaining;
11042
11043     if (first.pr == NoProc) {
11044         StartChessProgram(&first);
11045     }
11046     if (init) {
11047             InitChessProgram(&first, startedFromSetupPosition);
11048     }
11049     DisplayTitle("");
11050     DisplayMessage("", "");
11051     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11052     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11053     ClearMap();        // [HGM] exclude: invalidate map
11054 }
11055
11056 void
11057 AutoPlayGameLoop ()
11058 {
11059     for (;;) {
11060         if (!AutoPlayOneMove())
11061           return;
11062         if (matchMode || appData.timeDelay == 0)
11063           continue;
11064         if (appData.timeDelay < 0)
11065           return;
11066         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11067         break;
11068     }
11069 }
11070
11071 void
11072 AnalyzeNextGame()
11073 {
11074     ReloadGame(1); // next game
11075 }
11076
11077 int
11078 AutoPlayOneMove ()
11079 {
11080     int fromX, fromY, toX, toY;
11081
11082     if (appData.debugMode) {
11083       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11084     }
11085
11086     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11087       return FALSE;
11088
11089     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11090       pvInfoList[currentMove].depth = programStats.depth;
11091       pvInfoList[currentMove].score = programStats.score;
11092       pvInfoList[currentMove].time  = 0;
11093       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11094     }
11095
11096     if (currentMove >= forwardMostMove) {
11097       if(gameMode == AnalyzeFile) {
11098           if(appData.loadGameIndex == -1) {
11099             GameEnds(EndOfFile, NULL, GE_FILE);
11100           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11101           } else {
11102           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11103         }
11104       }
11105 //      gameMode = EndOfGame;
11106 //      ModeHighlight();
11107
11108       /* [AS] Clear current move marker at the end of a game */
11109       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11110
11111       return FALSE;
11112     }
11113
11114     toX = moveList[currentMove][2] - AAA;
11115     toY = moveList[currentMove][3] - ONE;
11116
11117     if (moveList[currentMove][1] == '@') {
11118         if (appData.highlightLastMove) {
11119             SetHighlights(-1, -1, toX, toY);
11120         }
11121     } else {
11122         fromX = moveList[currentMove][0] - AAA;
11123         fromY = moveList[currentMove][1] - ONE;
11124
11125         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11126
11127         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11128
11129         if (appData.highlightLastMove) {
11130             SetHighlights(fromX, fromY, toX, toY);
11131         }
11132     }
11133     DisplayMove(currentMove);
11134     SendMoveToProgram(currentMove++, &first);
11135     DisplayBothClocks();
11136     DrawPosition(FALSE, boards[currentMove]);
11137     // [HGM] PV info: always display, routine tests if empty
11138     DisplayComment(currentMove - 1, commentList[currentMove]);
11139     return TRUE;
11140 }
11141
11142
11143 int
11144 LoadGameOneMove (ChessMove readAhead)
11145 {
11146     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11147     char promoChar = NULLCHAR;
11148     ChessMove moveType;
11149     char move[MSG_SIZ];
11150     char *p, *q;
11151
11152     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11153         gameMode != AnalyzeMode && gameMode != Training) {
11154         gameFileFP = NULL;
11155         return FALSE;
11156     }
11157
11158     yyboardindex = forwardMostMove;
11159     if (readAhead != EndOfFile) {
11160       moveType = readAhead;
11161     } else {
11162       if (gameFileFP == NULL)
11163           return FALSE;
11164       moveType = (ChessMove) Myylex();
11165     }
11166
11167     done = FALSE;
11168     switch (moveType) {
11169       case Comment:
11170         if (appData.debugMode)
11171           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11172         p = yy_text;
11173
11174         /* append the comment but don't display it */
11175         AppendComment(currentMove, p, FALSE);
11176         return TRUE;
11177
11178       case WhiteCapturesEnPassant:
11179       case BlackCapturesEnPassant:
11180       case WhitePromotion:
11181       case BlackPromotion:
11182       case WhiteNonPromotion:
11183       case BlackNonPromotion:
11184       case NormalMove:
11185       case WhiteKingSideCastle:
11186       case WhiteQueenSideCastle:
11187       case BlackKingSideCastle:
11188       case BlackQueenSideCastle:
11189       case WhiteKingSideCastleWild:
11190       case WhiteQueenSideCastleWild:
11191       case BlackKingSideCastleWild:
11192       case BlackQueenSideCastleWild:
11193       /* PUSH Fabien */
11194       case WhiteHSideCastleFR:
11195       case WhiteASideCastleFR:
11196       case BlackHSideCastleFR:
11197       case BlackASideCastleFR:
11198       /* POP Fabien */
11199         if (appData.debugMode)
11200           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11201         fromX = currentMoveString[0] - AAA;
11202         fromY = currentMoveString[1] - ONE;
11203         toX = currentMoveString[2] - AAA;
11204         toY = currentMoveString[3] - ONE;
11205         promoChar = currentMoveString[4];
11206         break;
11207
11208       case WhiteDrop:
11209       case BlackDrop:
11210         if (appData.debugMode)
11211           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11212         fromX = moveType == WhiteDrop ?
11213           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11214         (int) CharToPiece(ToLower(currentMoveString[0]));
11215         fromY = DROP_RANK;
11216         toX = currentMoveString[2] - AAA;
11217         toY = currentMoveString[3] - ONE;
11218         break;
11219
11220       case WhiteWins:
11221       case BlackWins:
11222       case GameIsDrawn:
11223       case GameUnfinished:
11224         if (appData.debugMode)
11225           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11226         p = strchr(yy_text, '{');
11227         if (p == NULL) p = strchr(yy_text, '(');
11228         if (p == NULL) {
11229             p = yy_text;
11230             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11231         } else {
11232             q = strchr(p, *p == '{' ? '}' : ')');
11233             if (q != NULL) *q = NULLCHAR;
11234             p++;
11235         }
11236         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11237         GameEnds(moveType, p, GE_FILE);
11238         done = TRUE;
11239         if (cmailMsgLoaded) {
11240             ClearHighlights();
11241             flipView = WhiteOnMove(currentMove);
11242             if (moveType == GameUnfinished) flipView = !flipView;
11243             if (appData.debugMode)
11244               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11245         }
11246         break;
11247
11248       case EndOfFile:
11249         if (appData.debugMode)
11250           fprintf(debugFP, "Parser hit end of file\n");
11251         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11252           case MT_NONE:
11253           case MT_CHECK:
11254             break;
11255           case MT_CHECKMATE:
11256           case MT_STAINMATE:
11257             if (WhiteOnMove(currentMove)) {
11258                 GameEnds(BlackWins, "Black mates", GE_FILE);
11259             } else {
11260                 GameEnds(WhiteWins, "White mates", GE_FILE);
11261             }
11262             break;
11263           case MT_STALEMATE:
11264             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11265             break;
11266         }
11267         done = TRUE;
11268         break;
11269
11270       case MoveNumberOne:
11271         if (lastLoadGameStart == GNUChessGame) {
11272             /* GNUChessGames have numbers, but they aren't move numbers */
11273             if (appData.debugMode)
11274               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11275                       yy_text, (int) moveType);
11276             return LoadGameOneMove(EndOfFile); /* tail recursion */
11277         }
11278         /* else fall thru */
11279
11280       case XBoardGame:
11281       case GNUChessGame:
11282       case PGNTag:
11283         /* Reached start of next game in file */
11284         if (appData.debugMode)
11285           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11286         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11287           case MT_NONE:
11288           case MT_CHECK:
11289             break;
11290           case MT_CHECKMATE:
11291           case MT_STAINMATE:
11292             if (WhiteOnMove(currentMove)) {
11293                 GameEnds(BlackWins, "Black mates", GE_FILE);
11294             } else {
11295                 GameEnds(WhiteWins, "White mates", GE_FILE);
11296             }
11297             break;
11298           case MT_STALEMATE:
11299             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11300             break;
11301         }
11302         done = TRUE;
11303         break;
11304
11305       case PositionDiagram:     /* should not happen; ignore */
11306       case ElapsedTime:         /* ignore */
11307       case NAG:                 /* ignore */
11308         if (appData.debugMode)
11309           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11310                   yy_text, (int) moveType);
11311         return LoadGameOneMove(EndOfFile); /* tail recursion */
11312
11313       case IllegalMove:
11314         if (appData.testLegality) {
11315             if (appData.debugMode)
11316               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11317             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11318                     (forwardMostMove / 2) + 1,
11319                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11320             DisplayError(move, 0);
11321             done = TRUE;
11322         } else {
11323             if (appData.debugMode)
11324               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11325                       yy_text, currentMoveString);
11326             fromX = currentMoveString[0] - AAA;
11327             fromY = currentMoveString[1] - ONE;
11328             toX = currentMoveString[2] - AAA;
11329             toY = currentMoveString[3] - ONE;
11330             promoChar = currentMoveString[4];
11331         }
11332         break;
11333
11334       case AmbiguousMove:
11335         if (appData.debugMode)
11336           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11337         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11338                 (forwardMostMove / 2) + 1,
11339                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11340         DisplayError(move, 0);
11341         done = TRUE;
11342         break;
11343
11344       default:
11345       case ImpossibleMove:
11346         if (appData.debugMode)
11347           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11348         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11349                 (forwardMostMove / 2) + 1,
11350                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11351         DisplayError(move, 0);
11352         done = TRUE;
11353         break;
11354     }
11355
11356     if (done) {
11357         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11358             DrawPosition(FALSE, boards[currentMove]);
11359             DisplayBothClocks();
11360             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11361               DisplayComment(currentMove - 1, commentList[currentMove]);
11362         }
11363         (void) StopLoadGameTimer();
11364         gameFileFP = NULL;
11365         cmailOldMove = forwardMostMove;
11366         return FALSE;
11367     } else {
11368         /* currentMoveString is set as a side-effect of yylex */
11369
11370         thinkOutput[0] = NULLCHAR;
11371         MakeMove(fromX, fromY, toX, toY, promoChar);
11372         currentMove = forwardMostMove;
11373         return TRUE;
11374     }
11375 }
11376
11377 /* Load the nth game from the given file */
11378 int
11379 LoadGameFromFile (char *filename, int n, char *title, int useList)
11380 {
11381     FILE *f;
11382     char buf[MSG_SIZ];
11383
11384     if (strcmp(filename, "-") == 0) {
11385         f = stdin;
11386         title = "stdin";
11387     } else {
11388         f = fopen(filename, "rb");
11389         if (f == NULL) {
11390           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11391             DisplayError(buf, errno);
11392             return FALSE;
11393         }
11394     }
11395     if (fseek(f, 0, 0) == -1) {
11396         /* f is not seekable; probably a pipe */
11397         useList = FALSE;
11398     }
11399     if (useList && n == 0) {
11400         int error = GameListBuild(f);
11401         if (error) {
11402             DisplayError(_("Cannot build game list"), error);
11403         } else if (!ListEmpty(&gameList) &&
11404                    ((ListGame *) gameList.tailPred)->number > 1) {
11405             GameListPopUp(f, title);
11406             return TRUE;
11407         }
11408         GameListDestroy();
11409         n = 1;
11410     }
11411     if (n == 0) n = 1;
11412     return LoadGame(f, n, title, FALSE);
11413 }
11414
11415
11416 void
11417 MakeRegisteredMove ()
11418 {
11419     int fromX, fromY, toX, toY;
11420     char promoChar;
11421     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11422         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11423           case CMAIL_MOVE:
11424           case CMAIL_DRAW:
11425             if (appData.debugMode)
11426               fprintf(debugFP, "Restoring %s for game %d\n",
11427                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11428
11429             thinkOutput[0] = NULLCHAR;
11430             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11431             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11432             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11433             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11434             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11435             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11436             MakeMove(fromX, fromY, toX, toY, promoChar);
11437             ShowMove(fromX, fromY, toX, toY);
11438
11439             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11440               case MT_NONE:
11441               case MT_CHECK:
11442                 break;
11443
11444               case MT_CHECKMATE:
11445               case MT_STAINMATE:
11446                 if (WhiteOnMove(currentMove)) {
11447                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11448                 } else {
11449                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11450                 }
11451                 break;
11452
11453               case MT_STALEMATE:
11454                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11455                 break;
11456             }
11457
11458             break;
11459
11460           case CMAIL_RESIGN:
11461             if (WhiteOnMove(currentMove)) {
11462                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11463             } else {
11464                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11465             }
11466             break;
11467
11468           case CMAIL_ACCEPT:
11469             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11470             break;
11471
11472           default:
11473             break;
11474         }
11475     }
11476
11477     return;
11478 }
11479
11480 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11481 int
11482 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11483 {
11484     int retVal;
11485
11486     if (gameNumber > nCmailGames) {
11487         DisplayError(_("No more games in this message"), 0);
11488         return FALSE;
11489     }
11490     if (f == lastLoadGameFP) {
11491         int offset = gameNumber - lastLoadGameNumber;
11492         if (offset == 0) {
11493             cmailMsg[0] = NULLCHAR;
11494             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11495                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11496                 nCmailMovesRegistered--;
11497             }
11498             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11499             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11500                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11501             }
11502         } else {
11503             if (! RegisterMove()) return FALSE;
11504         }
11505     }
11506
11507     retVal = LoadGame(f, gameNumber, title, useList);
11508
11509     /* Make move registered during previous look at this game, if any */
11510     MakeRegisteredMove();
11511
11512     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11513         commentList[currentMove]
11514           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11515         DisplayComment(currentMove - 1, commentList[currentMove]);
11516     }
11517
11518     return retVal;
11519 }
11520
11521 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11522 int
11523 ReloadGame (int offset)
11524 {
11525     int gameNumber = lastLoadGameNumber + offset;
11526     if (lastLoadGameFP == NULL) {
11527         DisplayError(_("No game has been loaded yet"), 0);
11528         return FALSE;
11529     }
11530     if (gameNumber <= 0) {
11531         DisplayError(_("Can't back up any further"), 0);
11532         return FALSE;
11533     }
11534     if (cmailMsgLoaded) {
11535         return CmailLoadGame(lastLoadGameFP, gameNumber,
11536                              lastLoadGameTitle, lastLoadGameUseList);
11537     } else {
11538         return LoadGame(lastLoadGameFP, gameNumber,
11539                         lastLoadGameTitle, lastLoadGameUseList);
11540     }
11541 }
11542
11543 int keys[EmptySquare+1];
11544
11545 int
11546 PositionMatches (Board b1, Board b2)
11547 {
11548     int r, f, sum=0;
11549     switch(appData.searchMode) {
11550         case 1: return CompareWithRights(b1, b2);
11551         case 2:
11552             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11553                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11554             }
11555             return TRUE;
11556         case 3:
11557             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11558               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11559                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11560             }
11561             return sum==0;
11562         case 4:
11563             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11564                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11565             }
11566             return sum==0;
11567     }
11568     return TRUE;
11569 }
11570
11571 #define Q_PROMO  4
11572 #define Q_EP     3
11573 #define Q_BCASTL 2
11574 #define Q_WCASTL 1
11575
11576 int pieceList[256], quickBoard[256];
11577 ChessSquare pieceType[256] = { EmptySquare };
11578 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11579 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11580 int soughtTotal, turn;
11581 Boolean epOK, flipSearch;
11582
11583 typedef struct {
11584     unsigned char piece, to;
11585 } Move;
11586
11587 #define DSIZE (250000)
11588
11589 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11590 Move *moveDatabase = initialSpace;
11591 unsigned int movePtr, dataSize = DSIZE;
11592
11593 int
11594 MakePieceList (Board board, int *counts)
11595 {
11596     int r, f, n=Q_PROMO, total=0;
11597     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11598     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11599         int sq = f + (r<<4);
11600         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11601             quickBoard[sq] = ++n;
11602             pieceList[n] = sq;
11603             pieceType[n] = board[r][f];
11604             counts[board[r][f]]++;
11605             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11606             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11607             total++;
11608         }
11609     }
11610     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11611     return total;
11612 }
11613
11614 void
11615 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11616 {
11617     int sq = fromX + (fromY<<4);
11618     int piece = quickBoard[sq];
11619     quickBoard[sq] = 0;
11620     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11621     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11622         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11623         moveDatabase[movePtr++].piece = Q_WCASTL;
11624         quickBoard[sq] = piece;
11625         piece = quickBoard[from]; quickBoard[from] = 0;
11626         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11627     } else
11628     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11629         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11630         moveDatabase[movePtr++].piece = Q_BCASTL;
11631         quickBoard[sq] = piece;
11632         piece = quickBoard[from]; quickBoard[from] = 0;
11633         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11634     } else
11635     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11636         quickBoard[(fromY<<4)+toX] = 0;
11637         moveDatabase[movePtr].piece = Q_EP;
11638         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11639         moveDatabase[movePtr].to = sq;
11640     } else
11641     if(promoPiece != pieceType[piece]) {
11642         moveDatabase[movePtr++].piece = Q_PROMO;
11643         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11644     }
11645     moveDatabase[movePtr].piece = piece;
11646     quickBoard[sq] = piece;
11647     movePtr++;
11648 }
11649
11650 int
11651 PackGame (Board board)
11652 {
11653     Move *newSpace = NULL;
11654     moveDatabase[movePtr].piece = 0; // terminate previous game
11655     if(movePtr > dataSize) {
11656         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11657         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11658         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11659         if(newSpace) {
11660             int i;
11661             Move *p = moveDatabase, *q = newSpace;
11662             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11663             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11664             moveDatabase = newSpace;
11665         } else { // calloc failed, we must be out of memory. Too bad...
11666             dataSize = 0; // prevent calloc events for all subsequent games
11667             return 0;     // and signal this one isn't cached
11668         }
11669     }
11670     movePtr++;
11671     MakePieceList(board, counts);
11672     return movePtr;
11673 }
11674
11675 int
11676 QuickCompare (Board board, int *minCounts, int *maxCounts)
11677 {   // compare according to search mode
11678     int r, f;
11679     switch(appData.searchMode)
11680     {
11681       case 1: // exact position match
11682         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11683         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11684             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11685         }
11686         break;
11687       case 2: // can have extra material on empty squares
11688         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11689             if(board[r][f] == EmptySquare) continue;
11690             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11691         }
11692         break;
11693       case 3: // material with exact Pawn structure
11694         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11695             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11696             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11697         } // fall through to material comparison
11698       case 4: // exact material
11699         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11700         break;
11701       case 6: // material range with given imbalance
11702         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11703         // fall through to range comparison
11704       case 5: // material range
11705         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11706     }
11707     return TRUE;
11708 }
11709
11710 int
11711 QuickScan (Board board, Move *move)
11712 {   // reconstruct game,and compare all positions in it
11713     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11714     do {
11715         int piece = move->piece;
11716         int to = move->to, from = pieceList[piece];
11717         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11718           if(!piece) return -1;
11719           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11720             piece = (++move)->piece;
11721             from = pieceList[piece];
11722             counts[pieceType[piece]]--;
11723             pieceType[piece] = (ChessSquare) move->to;
11724             counts[move->to]++;
11725           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11726             counts[pieceType[quickBoard[to]]]--;
11727             quickBoard[to] = 0; total--;
11728             move++;
11729             continue;
11730           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11731             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11732             from  = pieceList[piece]; // so this must be King
11733             quickBoard[from] = 0;
11734             pieceList[piece] = to;
11735             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11736             quickBoard[from] = 0; // rook
11737             quickBoard[to] = piece;
11738             to = move->to; piece = move->piece;
11739             goto aftercastle;
11740           }
11741         }
11742         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11743         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11744         quickBoard[from] = 0;
11745       aftercastle:
11746         quickBoard[to] = piece;
11747         pieceList[piece] = to;
11748         cnt++; turn ^= 3;
11749         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11750            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11751            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11752                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11753           ) {
11754             static int lastCounts[EmptySquare+1];
11755             int i;
11756             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11757             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11758         } else stretch = 0;
11759         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11760         move++;
11761     } while(1);
11762 }
11763
11764 void
11765 InitSearch ()
11766 {
11767     int r, f;
11768     flipSearch = FALSE;
11769     CopyBoard(soughtBoard, boards[currentMove]);
11770     soughtTotal = MakePieceList(soughtBoard, maxSought);
11771     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11772     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11773     CopyBoard(reverseBoard, boards[currentMove]);
11774     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11775         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11776         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11777         reverseBoard[r][f] = piece;
11778     }
11779     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11780     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11781     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11782                  || (boards[currentMove][CASTLING][2] == NoRights || 
11783                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11784                  && (boards[currentMove][CASTLING][5] == NoRights || 
11785                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11786       ) {
11787         flipSearch = TRUE;
11788         CopyBoard(flipBoard, soughtBoard);
11789         CopyBoard(rotateBoard, reverseBoard);
11790         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11791             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11792             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11793         }
11794     }
11795     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11796     if(appData.searchMode >= 5) {
11797         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11798         MakePieceList(soughtBoard, minSought);
11799         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11800     }
11801     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11802         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11803 }
11804
11805 GameInfo dummyInfo;
11806 static int creatingBook;
11807
11808 int
11809 GameContainsPosition (FILE *f, ListGame *lg)
11810 {
11811     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11812     int fromX, fromY, toX, toY;
11813     char promoChar;
11814     static int initDone=FALSE;
11815
11816     // weed out games based on numerical tag comparison
11817     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11818     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11819     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11820     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11821     if(!initDone) {
11822         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11823         initDone = TRUE;
11824     }
11825     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11826     else CopyBoard(boards[scratch], initialPosition); // default start position
11827     if(lg->moves) {
11828         turn = btm + 1;
11829         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11830         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11831     }
11832     if(btm) plyNr++;
11833     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11834     fseek(f, lg->offset, 0);
11835     yynewfile(f);
11836     while(1) {
11837         yyboardindex = scratch;
11838         quickFlag = plyNr+1;
11839         next = Myylex();
11840         quickFlag = 0;
11841         switch(next) {
11842             case PGNTag:
11843                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11844             default:
11845                 continue;
11846
11847             case XBoardGame:
11848             case GNUChessGame:
11849                 if(plyNr) return -1; // after we have seen moves, this is for new game
11850               continue;
11851
11852             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11853             case ImpossibleMove:
11854             case WhiteWins: // game ends here with these four
11855             case BlackWins:
11856             case GameIsDrawn:
11857             case GameUnfinished:
11858                 return -1;
11859
11860             case IllegalMove:
11861                 if(appData.testLegality) return -1;
11862             case WhiteCapturesEnPassant:
11863             case BlackCapturesEnPassant:
11864             case WhitePromotion:
11865             case BlackPromotion:
11866             case WhiteNonPromotion:
11867             case BlackNonPromotion:
11868             case NormalMove:
11869             case WhiteKingSideCastle:
11870             case WhiteQueenSideCastle:
11871             case BlackKingSideCastle:
11872             case BlackQueenSideCastle:
11873             case WhiteKingSideCastleWild:
11874             case WhiteQueenSideCastleWild:
11875             case BlackKingSideCastleWild:
11876             case BlackQueenSideCastleWild:
11877             case WhiteHSideCastleFR:
11878             case WhiteASideCastleFR:
11879             case BlackHSideCastleFR:
11880             case BlackASideCastleFR:
11881                 fromX = currentMoveString[0] - AAA;
11882                 fromY = currentMoveString[1] - ONE;
11883                 toX = currentMoveString[2] - AAA;
11884                 toY = currentMoveString[3] - ONE;
11885                 promoChar = currentMoveString[4];
11886                 break;
11887             case WhiteDrop:
11888             case BlackDrop:
11889                 fromX = next == WhiteDrop ?
11890                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11891                   (int) CharToPiece(ToLower(currentMoveString[0]));
11892                 fromY = DROP_RANK;
11893                 toX = currentMoveString[2] - AAA;
11894                 toY = currentMoveString[3] - ONE;
11895                 promoChar = 0;
11896                 break;
11897         }
11898         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11899         plyNr++;
11900         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11901         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11902         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11903         if(appData.findMirror) {
11904             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11905             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11906         }
11907     }
11908 }
11909
11910 /* Load the nth game from open file f */
11911 int
11912 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11913 {
11914     ChessMove cm;
11915     char buf[MSG_SIZ];
11916     int gn = gameNumber;
11917     ListGame *lg = NULL;
11918     int numPGNTags = 0;
11919     int err, pos = -1;
11920     GameMode oldGameMode;
11921     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11922
11923     if (appData.debugMode)
11924         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11925
11926     if (gameMode == Training )
11927         SetTrainingModeOff();
11928
11929     oldGameMode = gameMode;
11930     if (gameMode != BeginningOfGame) {
11931       Reset(FALSE, TRUE);
11932     }
11933
11934     gameFileFP = f;
11935     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11936         fclose(lastLoadGameFP);
11937     }
11938
11939     if (useList) {
11940         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11941
11942         if (lg) {
11943             fseek(f, lg->offset, 0);
11944             GameListHighlight(gameNumber);
11945             pos = lg->position;
11946             gn = 1;
11947         }
11948         else {
11949             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11950               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11951             else
11952             DisplayError(_("Game number out of range"), 0);
11953             return FALSE;
11954         }
11955     } else {
11956         GameListDestroy();
11957         if (fseek(f, 0, 0) == -1) {
11958             if (f == lastLoadGameFP ?
11959                 gameNumber == lastLoadGameNumber + 1 :
11960                 gameNumber == 1) {
11961                 gn = 1;
11962             } else {
11963                 DisplayError(_("Can't seek on game file"), 0);
11964                 return FALSE;
11965             }
11966         }
11967     }
11968     lastLoadGameFP = f;
11969     lastLoadGameNumber = gameNumber;
11970     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11971     lastLoadGameUseList = useList;
11972
11973     yynewfile(f);
11974
11975     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11976       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11977                 lg->gameInfo.black);
11978             DisplayTitle(buf);
11979     } else if (*title != NULLCHAR) {
11980         if (gameNumber > 1) {
11981           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11982             DisplayTitle(buf);
11983         } else {
11984             DisplayTitle(title);
11985         }
11986     }
11987
11988     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11989         gameMode = PlayFromGameFile;
11990         ModeHighlight();
11991     }
11992
11993     currentMove = forwardMostMove = backwardMostMove = 0;
11994     CopyBoard(boards[0], initialPosition);
11995     StopClocks();
11996
11997     /*
11998      * Skip the first gn-1 games in the file.
11999      * Also skip over anything that precedes an identifiable
12000      * start of game marker, to avoid being confused by
12001      * garbage at the start of the file.  Currently
12002      * recognized start of game markers are the move number "1",
12003      * the pattern "gnuchess .* game", the pattern
12004      * "^[#;%] [^ ]* game file", and a PGN tag block.
12005      * A game that starts with one of the latter two patterns
12006      * will also have a move number 1, possibly
12007      * following a position diagram.
12008      * 5-4-02: Let's try being more lenient and allowing a game to
12009      * start with an unnumbered move.  Does that break anything?
12010      */
12011     cm = lastLoadGameStart = EndOfFile;
12012     while (gn > 0) {
12013         yyboardindex = forwardMostMove;
12014         cm = (ChessMove) Myylex();
12015         switch (cm) {
12016           case EndOfFile:
12017             if (cmailMsgLoaded) {
12018                 nCmailGames = CMAIL_MAX_GAMES - gn;
12019             } else {
12020                 Reset(TRUE, TRUE);
12021                 DisplayError(_("Game not found in file"), 0);
12022             }
12023             return FALSE;
12024
12025           case GNUChessGame:
12026           case XBoardGame:
12027             gn--;
12028             lastLoadGameStart = cm;
12029             break;
12030
12031           case MoveNumberOne:
12032             switch (lastLoadGameStart) {
12033               case GNUChessGame:
12034               case XBoardGame:
12035               case PGNTag:
12036                 break;
12037               case MoveNumberOne:
12038               case EndOfFile:
12039                 gn--;           /* count this game */
12040                 lastLoadGameStart = cm;
12041                 break;
12042               default:
12043                 /* impossible */
12044                 break;
12045             }
12046             break;
12047
12048           case PGNTag:
12049             switch (lastLoadGameStart) {
12050               case GNUChessGame:
12051               case PGNTag:
12052               case MoveNumberOne:
12053               case EndOfFile:
12054                 gn--;           /* count this game */
12055                 lastLoadGameStart = cm;
12056                 break;
12057               case XBoardGame:
12058                 lastLoadGameStart = cm; /* game counted already */
12059                 break;
12060               default:
12061                 /* impossible */
12062                 break;
12063             }
12064             if (gn > 0) {
12065                 do {
12066                     yyboardindex = forwardMostMove;
12067                     cm = (ChessMove) Myylex();
12068                 } while (cm == PGNTag || cm == Comment);
12069             }
12070             break;
12071
12072           case WhiteWins:
12073           case BlackWins:
12074           case GameIsDrawn:
12075             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12076                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12077                     != CMAIL_OLD_RESULT) {
12078                     nCmailResults ++ ;
12079                     cmailResult[  CMAIL_MAX_GAMES
12080                                 - gn - 1] = CMAIL_OLD_RESULT;
12081                 }
12082             }
12083             break;
12084
12085           case NormalMove:
12086             /* Only a NormalMove can be at the start of a game
12087              * without a position diagram. */
12088             if (lastLoadGameStart == EndOfFile ) {
12089               gn--;
12090               lastLoadGameStart = MoveNumberOne;
12091             }
12092             break;
12093
12094           default:
12095             break;
12096         }
12097     }
12098
12099     if (appData.debugMode)
12100       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12101
12102     if (cm == XBoardGame) {
12103         /* Skip any header junk before position diagram and/or move 1 */
12104         for (;;) {
12105             yyboardindex = forwardMostMove;
12106             cm = (ChessMove) Myylex();
12107
12108             if (cm == EndOfFile ||
12109                 cm == GNUChessGame || cm == XBoardGame) {
12110                 /* Empty game; pretend end-of-file and handle later */
12111                 cm = EndOfFile;
12112                 break;
12113             }
12114
12115             if (cm == MoveNumberOne || cm == PositionDiagram ||
12116                 cm == PGNTag || cm == Comment)
12117               break;
12118         }
12119     } else if (cm == GNUChessGame) {
12120         if (gameInfo.event != NULL) {
12121             free(gameInfo.event);
12122         }
12123         gameInfo.event = StrSave(yy_text);
12124     }
12125
12126     startedFromSetupPosition = FALSE;
12127     while (cm == PGNTag) {
12128         if (appData.debugMode)
12129           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12130         err = ParsePGNTag(yy_text, &gameInfo);
12131         if (!err) numPGNTags++;
12132
12133         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12134         if(gameInfo.variant != oldVariant) {
12135             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12136             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12137             InitPosition(TRUE);
12138             oldVariant = gameInfo.variant;
12139             if (appData.debugMode)
12140               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12141         }
12142
12143
12144         if (gameInfo.fen != NULL) {
12145           Board initial_position;
12146           startedFromSetupPosition = TRUE;
12147           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12148             Reset(TRUE, TRUE);
12149             DisplayError(_("Bad FEN position in file"), 0);
12150             return FALSE;
12151           }
12152           CopyBoard(boards[0], initial_position);
12153           if (blackPlaysFirst) {
12154             currentMove = forwardMostMove = backwardMostMove = 1;
12155             CopyBoard(boards[1], initial_position);
12156             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12157             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12158             timeRemaining[0][1] = whiteTimeRemaining;
12159             timeRemaining[1][1] = blackTimeRemaining;
12160             if (commentList[0] != NULL) {
12161               commentList[1] = commentList[0];
12162               commentList[0] = NULL;
12163             }
12164           } else {
12165             currentMove = forwardMostMove = backwardMostMove = 0;
12166           }
12167           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12168           {   int i;
12169               initialRulePlies = FENrulePlies;
12170               for( i=0; i< nrCastlingRights; i++ )
12171                   initialRights[i] = initial_position[CASTLING][i];
12172           }
12173           yyboardindex = forwardMostMove;
12174           free(gameInfo.fen);
12175           gameInfo.fen = NULL;
12176         }
12177
12178         yyboardindex = forwardMostMove;
12179         cm = (ChessMove) Myylex();
12180
12181         /* Handle comments interspersed among the tags */
12182         while (cm == Comment) {
12183             char *p;
12184             if (appData.debugMode)
12185               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12186             p = yy_text;
12187             AppendComment(currentMove, p, FALSE);
12188             yyboardindex = forwardMostMove;
12189             cm = (ChessMove) Myylex();
12190         }
12191     }
12192
12193     /* don't rely on existence of Event tag since if game was
12194      * pasted from clipboard the Event tag may not exist
12195      */
12196     if (numPGNTags > 0){
12197         char *tags;
12198         if (gameInfo.variant == VariantNormal) {
12199           VariantClass v = StringToVariant(gameInfo.event);
12200           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12201           if(v < VariantShogi) gameInfo.variant = v;
12202         }
12203         if (!matchMode) {
12204           if( appData.autoDisplayTags ) {
12205             tags = PGNTags(&gameInfo);
12206             TagsPopUp(tags, CmailMsg());
12207             free(tags);
12208           }
12209         }
12210     } else {
12211         /* Make something up, but don't display it now */
12212         SetGameInfo();
12213         TagsPopDown();
12214     }
12215
12216     if (cm == PositionDiagram) {
12217         int i, j;
12218         char *p;
12219         Board initial_position;
12220
12221         if (appData.debugMode)
12222           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12223
12224         if (!startedFromSetupPosition) {
12225             p = yy_text;
12226             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12227               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12228                 switch (*p) {
12229                   case '{':
12230                   case '[':
12231                   case '-':
12232                   case ' ':
12233                   case '\t':
12234                   case '\n':
12235                   case '\r':
12236                     break;
12237                   default:
12238                     initial_position[i][j++] = CharToPiece(*p);
12239                     break;
12240                 }
12241             while (*p == ' ' || *p == '\t' ||
12242                    *p == '\n' || *p == '\r') p++;
12243
12244             if (strncmp(p, "black", strlen("black"))==0)
12245               blackPlaysFirst = TRUE;
12246             else
12247               blackPlaysFirst = FALSE;
12248             startedFromSetupPosition = TRUE;
12249
12250             CopyBoard(boards[0], initial_position);
12251             if (blackPlaysFirst) {
12252                 currentMove = forwardMostMove = backwardMostMove = 1;
12253                 CopyBoard(boards[1], initial_position);
12254                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12255                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12256                 timeRemaining[0][1] = whiteTimeRemaining;
12257                 timeRemaining[1][1] = blackTimeRemaining;
12258                 if (commentList[0] != NULL) {
12259                     commentList[1] = commentList[0];
12260                     commentList[0] = NULL;
12261                 }
12262             } else {
12263                 currentMove = forwardMostMove = backwardMostMove = 0;
12264             }
12265         }
12266         yyboardindex = forwardMostMove;
12267         cm = (ChessMove) Myylex();
12268     }
12269
12270   if(!creatingBook) {
12271     if (first.pr == NoProc) {
12272         StartChessProgram(&first);
12273     }
12274     InitChessProgram(&first, FALSE);
12275     SendToProgram("force\n", &first);
12276     if (startedFromSetupPosition) {
12277         SendBoard(&first, forwardMostMove);
12278     if (appData.debugMode) {
12279         fprintf(debugFP, "Load Game\n");
12280     }
12281         DisplayBothClocks();
12282     }
12283   }
12284
12285     /* [HGM] server: flag to write setup moves in broadcast file as one */
12286     loadFlag = appData.suppressLoadMoves;
12287
12288     while (cm == Comment) {
12289         char *p;
12290         if (appData.debugMode)
12291           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12292         p = yy_text;
12293         AppendComment(currentMove, p, FALSE);
12294         yyboardindex = forwardMostMove;
12295         cm = (ChessMove) Myylex();
12296     }
12297
12298     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12299         cm == WhiteWins || cm == BlackWins ||
12300         cm == GameIsDrawn || cm == GameUnfinished) {
12301         DisplayMessage("", _("No moves in game"));
12302         if (cmailMsgLoaded) {
12303             if (appData.debugMode)
12304               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12305             ClearHighlights();
12306             flipView = FALSE;
12307         }
12308         DrawPosition(FALSE, boards[currentMove]);
12309         DisplayBothClocks();
12310         gameMode = EditGame;
12311         ModeHighlight();
12312         gameFileFP = NULL;
12313         cmailOldMove = 0;
12314         return TRUE;
12315     }
12316
12317     // [HGM] PV info: routine tests if comment empty
12318     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12319         DisplayComment(currentMove - 1, commentList[currentMove]);
12320     }
12321     if (!matchMode && appData.timeDelay != 0)
12322       DrawPosition(FALSE, boards[currentMove]);
12323
12324     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12325       programStats.ok_to_send = 1;
12326     }
12327
12328     /* if the first token after the PGN tags is a move
12329      * and not move number 1, retrieve it from the parser
12330      */
12331     if (cm != MoveNumberOne)
12332         LoadGameOneMove(cm);
12333
12334     /* load the remaining moves from the file */
12335     while (LoadGameOneMove(EndOfFile)) {
12336       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12337       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12338     }
12339
12340     /* rewind to the start of the game */
12341     currentMove = backwardMostMove;
12342
12343     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12344
12345     if (oldGameMode == AnalyzeFile ||
12346         oldGameMode == AnalyzeMode) {
12347       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12348       AnalyzeFileEvent();
12349     }
12350
12351     if(creatingBook) return TRUE;
12352     if (!matchMode && pos > 0) {
12353         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12354     } else
12355     if (matchMode || appData.timeDelay == 0) {
12356       ToEndEvent();
12357     } else if (appData.timeDelay > 0) {
12358       AutoPlayGameLoop();
12359     }
12360
12361     if (appData.debugMode)
12362         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12363
12364     loadFlag = 0; /* [HGM] true game starts */
12365     return TRUE;
12366 }
12367
12368 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12369 int
12370 ReloadPosition (int offset)
12371 {
12372     int positionNumber = lastLoadPositionNumber + offset;
12373     if (lastLoadPositionFP == NULL) {
12374         DisplayError(_("No position has been loaded yet"), 0);
12375         return FALSE;
12376     }
12377     if (positionNumber <= 0) {
12378         DisplayError(_("Can't back up any further"), 0);
12379         return FALSE;
12380     }
12381     return LoadPosition(lastLoadPositionFP, positionNumber,
12382                         lastLoadPositionTitle);
12383 }
12384
12385 /* Load the nth position from the given file */
12386 int
12387 LoadPositionFromFile (char *filename, int n, char *title)
12388 {
12389     FILE *f;
12390     char buf[MSG_SIZ];
12391
12392     if (strcmp(filename, "-") == 0) {
12393         return LoadPosition(stdin, n, "stdin");
12394     } else {
12395         f = fopen(filename, "rb");
12396         if (f == NULL) {
12397             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12398             DisplayError(buf, errno);
12399             return FALSE;
12400         } else {
12401             return LoadPosition(f, n, title);
12402         }
12403     }
12404 }
12405
12406 /* Load the nth position from the given open file, and close it */
12407 int
12408 LoadPosition (FILE *f, int positionNumber, char *title)
12409 {
12410     char *p, line[MSG_SIZ];
12411     Board initial_position;
12412     int i, j, fenMode, pn;
12413
12414     if (gameMode == Training )
12415         SetTrainingModeOff();
12416
12417     if (gameMode != BeginningOfGame) {
12418         Reset(FALSE, TRUE);
12419     }
12420     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12421         fclose(lastLoadPositionFP);
12422     }
12423     if (positionNumber == 0) positionNumber = 1;
12424     lastLoadPositionFP = f;
12425     lastLoadPositionNumber = positionNumber;
12426     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12427     if (first.pr == NoProc && !appData.noChessProgram) {
12428       StartChessProgram(&first);
12429       InitChessProgram(&first, FALSE);
12430     }
12431     pn = positionNumber;
12432     if (positionNumber < 0) {
12433         /* Negative position number means to seek to that byte offset */
12434         if (fseek(f, -positionNumber, 0) == -1) {
12435             DisplayError(_("Can't seek on position file"), 0);
12436             return FALSE;
12437         };
12438         pn = 1;
12439     } else {
12440         if (fseek(f, 0, 0) == -1) {
12441             if (f == lastLoadPositionFP ?
12442                 positionNumber == lastLoadPositionNumber + 1 :
12443                 positionNumber == 1) {
12444                 pn = 1;
12445             } else {
12446                 DisplayError(_("Can't seek on position file"), 0);
12447                 return FALSE;
12448             }
12449         }
12450     }
12451     /* See if this file is FEN or old-style xboard */
12452     if (fgets(line, MSG_SIZ, f) == NULL) {
12453         DisplayError(_("Position not found in file"), 0);
12454         return FALSE;
12455     }
12456     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12457     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12458
12459     if (pn >= 2) {
12460         if (fenMode || line[0] == '#') pn--;
12461         while (pn > 0) {
12462             /* skip positions before number pn */
12463             if (fgets(line, MSG_SIZ, f) == NULL) {
12464                 Reset(TRUE, TRUE);
12465                 DisplayError(_("Position not found in file"), 0);
12466                 return FALSE;
12467             }
12468             if (fenMode || line[0] == '#') pn--;
12469         }
12470     }
12471
12472     if (fenMode) {
12473         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12474             DisplayError(_("Bad FEN position in file"), 0);
12475             return FALSE;
12476         }
12477     } else {
12478         (void) fgets(line, MSG_SIZ, f);
12479         (void) fgets(line, MSG_SIZ, f);
12480
12481         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12482             (void) fgets(line, MSG_SIZ, f);
12483             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12484                 if (*p == ' ')
12485                   continue;
12486                 initial_position[i][j++] = CharToPiece(*p);
12487             }
12488         }
12489
12490         blackPlaysFirst = FALSE;
12491         if (!feof(f)) {
12492             (void) fgets(line, MSG_SIZ, f);
12493             if (strncmp(line, "black", strlen("black"))==0)
12494               blackPlaysFirst = TRUE;
12495         }
12496     }
12497     startedFromSetupPosition = TRUE;
12498
12499     CopyBoard(boards[0], initial_position);
12500     if (blackPlaysFirst) {
12501         currentMove = forwardMostMove = backwardMostMove = 1;
12502         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12503         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12504         CopyBoard(boards[1], initial_position);
12505         DisplayMessage("", _("Black to play"));
12506     } else {
12507         currentMove = forwardMostMove = backwardMostMove = 0;
12508         DisplayMessage("", _("White to play"));
12509     }
12510     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12511     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12512         SendToProgram("force\n", &first);
12513         SendBoard(&first, forwardMostMove);
12514     }
12515     if (appData.debugMode) {
12516 int i, j;
12517   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12518   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12519         fprintf(debugFP, "Load Position\n");
12520     }
12521
12522     if (positionNumber > 1) {
12523       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12524         DisplayTitle(line);
12525     } else {
12526         DisplayTitle(title);
12527     }
12528     gameMode = EditGame;
12529     ModeHighlight();
12530     ResetClocks();
12531     timeRemaining[0][1] = whiteTimeRemaining;
12532     timeRemaining[1][1] = blackTimeRemaining;
12533     DrawPosition(FALSE, boards[currentMove]);
12534
12535     return TRUE;
12536 }
12537
12538
12539 void
12540 CopyPlayerNameIntoFileName (char **dest, char *src)
12541 {
12542     while (*src != NULLCHAR && *src != ',') {
12543         if (*src == ' ') {
12544             *(*dest)++ = '_';
12545             src++;
12546         } else {
12547             *(*dest)++ = *src++;
12548         }
12549     }
12550 }
12551
12552 char *
12553 DefaultFileName (char *ext)
12554 {
12555     static char def[MSG_SIZ];
12556     char *p;
12557
12558     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12559         p = def;
12560         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12561         *p++ = '-';
12562         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12563         *p++ = '.';
12564         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12565     } else {
12566         def[0] = NULLCHAR;
12567     }
12568     return def;
12569 }
12570
12571 /* Save the current game to the given file */
12572 int
12573 SaveGameToFile (char *filename, int append)
12574 {
12575     FILE *f;
12576     char buf[MSG_SIZ];
12577     int result, i, t,tot=0;
12578
12579     if (strcmp(filename, "-") == 0) {
12580         return SaveGame(stdout, 0, NULL);
12581     } else {
12582         for(i=0; i<10; i++) { // upto 10 tries
12583              f = fopen(filename, append ? "a" : "w");
12584              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12585              if(f || errno != 13) break;
12586              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12587              tot += t;
12588         }
12589         if (f == NULL) {
12590             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12591             DisplayError(buf, errno);
12592             return FALSE;
12593         } else {
12594             safeStrCpy(buf, lastMsg, MSG_SIZ);
12595             DisplayMessage(_("Waiting for access to save file"), "");
12596             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12597             DisplayMessage(_("Saving game"), "");
12598             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12599             result = SaveGame(f, 0, NULL);
12600             DisplayMessage(buf, "");
12601             return result;
12602         }
12603     }
12604 }
12605
12606 char *
12607 SavePart (char *str)
12608 {
12609     static char buf[MSG_SIZ];
12610     char *p;
12611
12612     p = strchr(str, ' ');
12613     if (p == NULL) return str;
12614     strncpy(buf, str, p - str);
12615     buf[p - str] = NULLCHAR;
12616     return buf;
12617 }
12618
12619 #define PGN_MAX_LINE 75
12620
12621 #define PGN_SIDE_WHITE  0
12622 #define PGN_SIDE_BLACK  1
12623
12624 static int
12625 FindFirstMoveOutOfBook (int side)
12626 {
12627     int result = -1;
12628
12629     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12630         int index = backwardMostMove;
12631         int has_book_hit = 0;
12632
12633         if( (index % 2) != side ) {
12634             index++;
12635         }
12636
12637         while( index < forwardMostMove ) {
12638             /* Check to see if engine is in book */
12639             int depth = pvInfoList[index].depth;
12640             int score = pvInfoList[index].score;
12641             int in_book = 0;
12642
12643             if( depth <= 2 ) {
12644                 in_book = 1;
12645             }
12646             else if( score == 0 && depth == 63 ) {
12647                 in_book = 1; /* Zappa */
12648             }
12649             else if( score == 2 && depth == 99 ) {
12650                 in_book = 1; /* Abrok */
12651             }
12652
12653             has_book_hit += in_book;
12654
12655             if( ! in_book ) {
12656                 result = index;
12657
12658                 break;
12659             }
12660
12661             index += 2;
12662         }
12663     }
12664
12665     return result;
12666 }
12667
12668 void
12669 GetOutOfBookInfo (char * buf)
12670 {
12671     int oob[2];
12672     int i;
12673     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12674
12675     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12676     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12677
12678     *buf = '\0';
12679
12680     if( oob[0] >= 0 || oob[1] >= 0 ) {
12681         for( i=0; i<2; i++ ) {
12682             int idx = oob[i];
12683
12684             if( idx >= 0 ) {
12685                 if( i > 0 && oob[0] >= 0 ) {
12686                     strcat( buf, "   " );
12687                 }
12688
12689                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12690                 sprintf( buf+strlen(buf), "%s%.2f",
12691                     pvInfoList[idx].score >= 0 ? "+" : "",
12692                     pvInfoList[idx].score / 100.0 );
12693             }
12694         }
12695     }
12696 }
12697
12698 /* Save game in PGN style and close the file */
12699 int
12700 SaveGamePGN (FILE *f)
12701 {
12702     int i, offset, linelen, newblock;
12703 //    char *movetext;
12704     char numtext[32];
12705     int movelen, numlen, blank;
12706     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12707
12708     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12709
12710     PrintPGNTags(f, &gameInfo);
12711
12712     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12713
12714     if (backwardMostMove > 0 || startedFromSetupPosition) {
12715         char *fen = PositionToFEN(backwardMostMove, NULL);
12716         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12717         fprintf(f, "\n{--------------\n");
12718         PrintPosition(f, backwardMostMove);
12719         fprintf(f, "--------------}\n");
12720         free(fen);
12721     }
12722     else {
12723         /* [AS] Out of book annotation */
12724         if( appData.saveOutOfBookInfo ) {
12725             char buf[64];
12726
12727             GetOutOfBookInfo( buf );
12728
12729             if( buf[0] != '\0' ) {
12730                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12731             }
12732         }
12733
12734         fprintf(f, "\n");
12735     }
12736
12737     i = backwardMostMove;
12738     linelen = 0;
12739     newblock = TRUE;
12740
12741     while (i < forwardMostMove) {
12742         /* Print comments preceding this move */
12743         if (commentList[i] != NULL) {
12744             if (linelen > 0) fprintf(f, "\n");
12745             fprintf(f, "%s", commentList[i]);
12746             linelen = 0;
12747             newblock = TRUE;
12748         }
12749
12750         /* Format move number */
12751         if ((i % 2) == 0)
12752           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12753         else
12754           if (newblock)
12755             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12756           else
12757             numtext[0] = NULLCHAR;
12758
12759         numlen = strlen(numtext);
12760         newblock = FALSE;
12761
12762         /* Print move number */
12763         blank = linelen > 0 && numlen > 0;
12764         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12765             fprintf(f, "\n");
12766             linelen = 0;
12767             blank = 0;
12768         }
12769         if (blank) {
12770             fprintf(f, " ");
12771             linelen++;
12772         }
12773         fprintf(f, "%s", numtext);
12774         linelen += numlen;
12775
12776         /* Get move */
12777         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12778         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12779
12780         /* Print move */
12781         blank = linelen > 0 && movelen > 0;
12782         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12783             fprintf(f, "\n");
12784             linelen = 0;
12785             blank = 0;
12786         }
12787         if (blank) {
12788             fprintf(f, " ");
12789             linelen++;
12790         }
12791         fprintf(f, "%s", move_buffer);
12792         linelen += movelen;
12793
12794         /* [AS] Add PV info if present */
12795         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12796             /* [HGM] add time */
12797             char buf[MSG_SIZ]; int seconds;
12798
12799             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12800
12801             if( seconds <= 0)
12802               buf[0] = 0;
12803             else
12804               if( seconds < 30 )
12805                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12806               else
12807                 {
12808                   seconds = (seconds + 4)/10; // round to full seconds
12809                   if( seconds < 60 )
12810                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12811                   else
12812                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12813                 }
12814
12815             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12816                       pvInfoList[i].score >= 0 ? "+" : "",
12817                       pvInfoList[i].score / 100.0,
12818                       pvInfoList[i].depth,
12819                       buf );
12820
12821             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12822
12823             /* Print score/depth */
12824             blank = linelen > 0 && movelen > 0;
12825             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12826                 fprintf(f, "\n");
12827                 linelen = 0;
12828                 blank = 0;
12829             }
12830             if (blank) {
12831                 fprintf(f, " ");
12832                 linelen++;
12833             }
12834             fprintf(f, "%s", move_buffer);
12835             linelen += movelen;
12836         }
12837
12838         i++;
12839     }
12840
12841     /* Start a new line */
12842     if (linelen > 0) fprintf(f, "\n");
12843
12844     /* Print comments after last move */
12845     if (commentList[i] != NULL) {
12846         fprintf(f, "%s\n", commentList[i]);
12847     }
12848
12849     /* Print result */
12850     if (gameInfo.resultDetails != NULL &&
12851         gameInfo.resultDetails[0] != NULLCHAR) {
12852         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12853                 PGNResult(gameInfo.result));
12854     } else {
12855         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12856     }
12857
12858     fclose(f);
12859     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12860     return TRUE;
12861 }
12862
12863 /* Save game in old style and close the file */
12864 int
12865 SaveGameOldStyle (FILE *f)
12866 {
12867     int i, offset;
12868     time_t tm;
12869
12870     tm = time((time_t *) NULL);
12871
12872     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12873     PrintOpponents(f);
12874
12875     if (backwardMostMove > 0 || startedFromSetupPosition) {
12876         fprintf(f, "\n[--------------\n");
12877         PrintPosition(f, backwardMostMove);
12878         fprintf(f, "--------------]\n");
12879     } else {
12880         fprintf(f, "\n");
12881     }
12882
12883     i = backwardMostMove;
12884     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12885
12886     while (i < forwardMostMove) {
12887         if (commentList[i] != NULL) {
12888             fprintf(f, "[%s]\n", commentList[i]);
12889         }
12890
12891         if ((i % 2) == 1) {
12892             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12893             i++;
12894         } else {
12895             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12896             i++;
12897             if (commentList[i] != NULL) {
12898                 fprintf(f, "\n");
12899                 continue;
12900             }
12901             if (i >= forwardMostMove) {
12902                 fprintf(f, "\n");
12903                 break;
12904             }
12905             fprintf(f, "%s\n", parseList[i]);
12906             i++;
12907         }
12908     }
12909
12910     if (commentList[i] != NULL) {
12911         fprintf(f, "[%s]\n", commentList[i]);
12912     }
12913
12914     /* This isn't really the old style, but it's close enough */
12915     if (gameInfo.resultDetails != NULL &&
12916         gameInfo.resultDetails[0] != NULLCHAR) {
12917         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12918                 gameInfo.resultDetails);
12919     } else {
12920         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12921     }
12922
12923     fclose(f);
12924     return TRUE;
12925 }
12926
12927 /* Save the current game to open file f and close the file */
12928 int
12929 SaveGame (FILE *f, int dummy, char *dummy2)
12930 {
12931     if (gameMode == EditPosition) EditPositionDone(TRUE);
12932     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12933     if (appData.oldSaveStyle)
12934       return SaveGameOldStyle(f);
12935     else
12936       return SaveGamePGN(f);
12937 }
12938
12939 /* Save the current position to the given file */
12940 int
12941 SavePositionToFile (char *filename)
12942 {
12943     FILE *f;
12944     char buf[MSG_SIZ];
12945
12946     if (strcmp(filename, "-") == 0) {
12947         return SavePosition(stdout, 0, NULL);
12948     } else {
12949         f = fopen(filename, "a");
12950         if (f == NULL) {
12951             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12952             DisplayError(buf, errno);
12953             return FALSE;
12954         } else {
12955             safeStrCpy(buf, lastMsg, MSG_SIZ);
12956             DisplayMessage(_("Waiting for access to save file"), "");
12957             flock(fileno(f), LOCK_EX); // [HGM] lock
12958             DisplayMessage(_("Saving position"), "");
12959             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12960             SavePosition(f, 0, NULL);
12961             DisplayMessage(buf, "");
12962             return TRUE;
12963         }
12964     }
12965 }
12966
12967 /* Save the current position to the given open file and close the file */
12968 int
12969 SavePosition (FILE *f, int dummy, char *dummy2)
12970 {
12971     time_t tm;
12972     char *fen;
12973
12974     if (gameMode == EditPosition) EditPositionDone(TRUE);
12975     if (appData.oldSaveStyle) {
12976         tm = time((time_t *) NULL);
12977
12978         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12979         PrintOpponents(f);
12980         fprintf(f, "[--------------\n");
12981         PrintPosition(f, currentMove);
12982         fprintf(f, "--------------]\n");
12983     } else {
12984         fen = PositionToFEN(currentMove, NULL);
12985         fprintf(f, "%s\n", fen);
12986         free(fen);
12987     }
12988     fclose(f);
12989     return TRUE;
12990 }
12991
12992 void
12993 ReloadCmailMsgEvent (int unregister)
12994 {
12995 #if !WIN32
12996     static char *inFilename = NULL;
12997     static char *outFilename;
12998     int i;
12999     struct stat inbuf, outbuf;
13000     int status;
13001
13002     /* Any registered moves are unregistered if unregister is set, */
13003     /* i.e. invoked by the signal handler */
13004     if (unregister) {
13005         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13006             cmailMoveRegistered[i] = FALSE;
13007             if (cmailCommentList[i] != NULL) {
13008                 free(cmailCommentList[i]);
13009                 cmailCommentList[i] = NULL;
13010             }
13011         }
13012         nCmailMovesRegistered = 0;
13013     }
13014
13015     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13016         cmailResult[i] = CMAIL_NOT_RESULT;
13017     }
13018     nCmailResults = 0;
13019
13020     if (inFilename == NULL) {
13021         /* Because the filenames are static they only get malloced once  */
13022         /* and they never get freed                                      */
13023         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13024         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13025
13026         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13027         sprintf(outFilename, "%s.out", appData.cmailGameName);
13028     }
13029
13030     status = stat(outFilename, &outbuf);
13031     if (status < 0) {
13032         cmailMailedMove = FALSE;
13033     } else {
13034         status = stat(inFilename, &inbuf);
13035         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13036     }
13037
13038     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13039        counts the games, notes how each one terminated, etc.
13040
13041        It would be nice to remove this kludge and instead gather all
13042        the information while building the game list.  (And to keep it
13043        in the game list nodes instead of having a bunch of fixed-size
13044        parallel arrays.)  Note this will require getting each game's
13045        termination from the PGN tags, as the game list builder does
13046        not process the game moves.  --mann
13047        */
13048     cmailMsgLoaded = TRUE;
13049     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13050
13051     /* Load first game in the file or popup game menu */
13052     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13053
13054 #endif /* !WIN32 */
13055     return;
13056 }
13057
13058 int
13059 RegisterMove ()
13060 {
13061     FILE *f;
13062     char string[MSG_SIZ];
13063
13064     if (   cmailMailedMove
13065         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13066         return TRUE;            /* Allow free viewing  */
13067     }
13068
13069     /* Unregister move to ensure that we don't leave RegisterMove        */
13070     /* with the move registered when the conditions for registering no   */
13071     /* longer hold                                                       */
13072     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13073         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13074         nCmailMovesRegistered --;
13075
13076         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13077           {
13078               free(cmailCommentList[lastLoadGameNumber - 1]);
13079               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13080           }
13081     }
13082
13083     if (cmailOldMove == -1) {
13084         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13085         return FALSE;
13086     }
13087
13088     if (currentMove > cmailOldMove + 1) {
13089         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13090         return FALSE;
13091     }
13092
13093     if (currentMove < cmailOldMove) {
13094         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13095         return FALSE;
13096     }
13097
13098     if (forwardMostMove > currentMove) {
13099         /* Silently truncate extra moves */
13100         TruncateGame();
13101     }
13102
13103     if (   (currentMove == cmailOldMove + 1)
13104         || (   (currentMove == cmailOldMove)
13105             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13106                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13107         if (gameInfo.result != GameUnfinished) {
13108             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13109         }
13110
13111         if (commentList[currentMove] != NULL) {
13112             cmailCommentList[lastLoadGameNumber - 1]
13113               = StrSave(commentList[currentMove]);
13114         }
13115         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13116
13117         if (appData.debugMode)
13118           fprintf(debugFP, "Saving %s for game %d\n",
13119                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13120
13121         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13122
13123         f = fopen(string, "w");
13124         if (appData.oldSaveStyle) {
13125             SaveGameOldStyle(f); /* also closes the file */
13126
13127             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13128             f = fopen(string, "w");
13129             SavePosition(f, 0, NULL); /* also closes the file */
13130         } else {
13131             fprintf(f, "{--------------\n");
13132             PrintPosition(f, currentMove);
13133             fprintf(f, "--------------}\n\n");
13134
13135             SaveGame(f, 0, NULL); /* also closes the file*/
13136         }
13137
13138         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13139         nCmailMovesRegistered ++;
13140     } else if (nCmailGames == 1) {
13141         DisplayError(_("You have not made a move yet"), 0);
13142         return FALSE;
13143     }
13144
13145     return TRUE;
13146 }
13147
13148 void
13149 MailMoveEvent ()
13150 {
13151 #if !WIN32
13152     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13153     FILE *commandOutput;
13154     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13155     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13156     int nBuffers;
13157     int i;
13158     int archived;
13159     char *arcDir;
13160
13161     if (! cmailMsgLoaded) {
13162         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13163         return;
13164     }
13165
13166     if (nCmailGames == nCmailResults) {
13167         DisplayError(_("No unfinished games"), 0);
13168         return;
13169     }
13170
13171 #if CMAIL_PROHIBIT_REMAIL
13172     if (cmailMailedMove) {
13173       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);
13174         DisplayError(msg, 0);
13175         return;
13176     }
13177 #endif
13178
13179     if (! (cmailMailedMove || RegisterMove())) return;
13180
13181     if (   cmailMailedMove
13182         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13183       snprintf(string, MSG_SIZ, partCommandString,
13184                appData.debugMode ? " -v" : "", appData.cmailGameName);
13185         commandOutput = popen(string, "r");
13186
13187         if (commandOutput == NULL) {
13188             DisplayError(_("Failed to invoke cmail"), 0);
13189         } else {
13190             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13191                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13192             }
13193             if (nBuffers > 1) {
13194                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13195                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13196                 nBytes = MSG_SIZ - 1;
13197             } else {
13198                 (void) memcpy(msg, buffer, nBytes);
13199             }
13200             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13201
13202             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13203                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13204
13205                 archived = TRUE;
13206                 for (i = 0; i < nCmailGames; i ++) {
13207                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13208                         archived = FALSE;
13209                     }
13210                 }
13211                 if (   archived
13212                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13213                         != NULL)) {
13214                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13215                            arcDir,
13216                            appData.cmailGameName,
13217                            gameInfo.date);
13218                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13219                     cmailMsgLoaded = FALSE;
13220                 }
13221             }
13222
13223             DisplayInformation(msg);
13224             pclose(commandOutput);
13225         }
13226     } else {
13227         if ((*cmailMsg) != '\0') {
13228             DisplayInformation(cmailMsg);
13229         }
13230     }
13231
13232     return;
13233 #endif /* !WIN32 */
13234 }
13235
13236 char *
13237 CmailMsg ()
13238 {
13239 #if WIN32
13240     return NULL;
13241 #else
13242     int  prependComma = 0;
13243     char number[5];
13244     char string[MSG_SIZ];       /* Space for game-list */
13245     int  i;
13246
13247     if (!cmailMsgLoaded) return "";
13248
13249     if (cmailMailedMove) {
13250       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13251     } else {
13252         /* Create a list of games left */
13253       snprintf(string, MSG_SIZ, "[");
13254         for (i = 0; i < nCmailGames; i ++) {
13255             if (! (   cmailMoveRegistered[i]
13256                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13257                 if (prependComma) {
13258                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13259                 } else {
13260                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13261                     prependComma = 1;
13262                 }
13263
13264                 strcat(string, number);
13265             }
13266         }
13267         strcat(string, "]");
13268
13269         if (nCmailMovesRegistered + nCmailResults == 0) {
13270             switch (nCmailGames) {
13271               case 1:
13272                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13273                 break;
13274
13275               case 2:
13276                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13277                 break;
13278
13279               default:
13280                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13281                          nCmailGames);
13282                 break;
13283             }
13284         } else {
13285             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13286               case 1:
13287                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13288                          string);
13289                 break;
13290
13291               case 0:
13292                 if (nCmailResults == nCmailGames) {
13293                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13294                 } else {
13295                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13296                 }
13297                 break;
13298
13299               default:
13300                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13301                          string);
13302             }
13303         }
13304     }
13305     return cmailMsg;
13306 #endif /* WIN32 */
13307 }
13308
13309 void
13310 ResetGameEvent ()
13311 {
13312     if (gameMode == Training)
13313       SetTrainingModeOff();
13314
13315     Reset(TRUE, TRUE);
13316     cmailMsgLoaded = FALSE;
13317     if (appData.icsActive) {
13318       SendToICS(ics_prefix);
13319       SendToICS("refresh\n");
13320     }
13321 }
13322
13323 void
13324 ExitEvent (int status)
13325 {
13326     exiting++;
13327     if (exiting > 2) {
13328       /* Give up on clean exit */
13329       exit(status);
13330     }
13331     if (exiting > 1) {
13332       /* Keep trying for clean exit */
13333       return;
13334     }
13335
13336     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13337
13338     if (telnetISR != NULL) {
13339       RemoveInputSource(telnetISR);
13340     }
13341     if (icsPR != NoProc) {
13342       DestroyChildProcess(icsPR, TRUE);
13343     }
13344
13345     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13346     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13347
13348     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13349     /* make sure this other one finishes before killing it!                  */
13350     if(endingGame) { int count = 0;
13351         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13352         while(endingGame && count++ < 10) DoSleep(1);
13353         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13354     }
13355
13356     /* Kill off chess programs */
13357     if (first.pr != NoProc) {
13358         ExitAnalyzeMode();
13359
13360         DoSleep( appData.delayBeforeQuit );
13361         SendToProgram("quit\n", &first);
13362         DoSleep( appData.delayAfterQuit );
13363         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13364     }
13365     if (second.pr != NoProc) {
13366         DoSleep( appData.delayBeforeQuit );
13367         SendToProgram("quit\n", &second);
13368         DoSleep( appData.delayAfterQuit );
13369         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13370     }
13371     if (first.isr != NULL) {
13372         RemoveInputSource(first.isr);
13373     }
13374     if (second.isr != NULL) {
13375         RemoveInputSource(second.isr);
13376     }
13377
13378     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13379     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13380
13381     ShutDownFrontEnd();
13382     exit(status);
13383 }
13384
13385 void
13386 PauseEngine (ChessProgramState *cps)
13387 {
13388     SendToProgram("pause\n", cps);
13389     cps->pause = 2;
13390 }
13391
13392 void
13393 UnPauseEngine (ChessProgramState *cps)
13394 {
13395     SendToProgram("resume\n", cps);
13396     cps->pause = 1;
13397 }
13398
13399 void
13400 PauseEvent ()
13401 {
13402     if (appData.debugMode)
13403         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13404     if (pausing) {
13405         pausing = FALSE;
13406         ModeHighlight();
13407         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13408             StartClocks();
13409             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13410                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13411                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13412             }
13413             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13414             HandleMachineMove(stashedInputMove, stalledEngine);
13415             stalledEngine = NULL;
13416             return;
13417         }
13418         if (gameMode == MachinePlaysWhite ||
13419             gameMode == TwoMachinesPlay   ||
13420             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13421             if(first.pause)  UnPauseEngine(&first);
13422             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13423             if(second.pause) UnPauseEngine(&second);
13424             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13425             StartClocks();
13426         } else {
13427             DisplayBothClocks();
13428         }
13429         if (gameMode == PlayFromGameFile) {
13430             if (appData.timeDelay >= 0)
13431                 AutoPlayGameLoop();
13432         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13433             Reset(FALSE, TRUE);
13434             SendToICS(ics_prefix);
13435             SendToICS("refresh\n");
13436         } else if (currentMove < forwardMostMove) {
13437             ForwardInner(forwardMostMove);
13438         }
13439         pauseExamInvalid = FALSE;
13440     } else {
13441         switch (gameMode) {
13442           default:
13443             return;
13444           case IcsExamining:
13445             pauseExamForwardMostMove = forwardMostMove;
13446             pauseExamInvalid = FALSE;
13447             /* fall through */
13448           case IcsObserving:
13449           case IcsPlayingWhite:
13450           case IcsPlayingBlack:
13451             pausing = TRUE;
13452             ModeHighlight();
13453             return;
13454           case PlayFromGameFile:
13455             (void) StopLoadGameTimer();
13456             pausing = TRUE;
13457             ModeHighlight();
13458             break;
13459           case BeginningOfGame:
13460             if (appData.icsActive) return;
13461             /* else fall through */
13462           case MachinePlaysWhite:
13463           case MachinePlaysBlack:
13464           case TwoMachinesPlay:
13465             if (forwardMostMove == 0)
13466               return;           /* don't pause if no one has moved */
13467             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13468                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13469                 if(onMove->pause) {           // thinking engine can be paused
13470                     PauseEngine(onMove);      // do it
13471                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13472                         PauseEngine(onMove->other);
13473                     else
13474                         SendToProgram("easy\n", onMove->other);
13475                     StopClocks();
13476                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13477             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13478                 if(first.pause) {
13479                     PauseEngine(&first);
13480                     StopClocks();
13481                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13482             } else { // human on move, pause pondering by either method
13483                 if(first.pause) 
13484                     PauseEngine(&first);
13485                 else if(appData.ponderNextMove) 
13486                     SendToProgram("easy\n", &first);
13487                 StopClocks();
13488             }
13489             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13490           case AnalyzeMode:
13491             pausing = TRUE;
13492             ModeHighlight();
13493             break;
13494         }
13495     }
13496 }
13497
13498 void
13499 EditCommentEvent ()
13500 {
13501     char title[MSG_SIZ];
13502
13503     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13504       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13505     } else {
13506       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13507                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13508                parseList[currentMove - 1]);
13509     }
13510
13511     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13512 }
13513
13514
13515 void
13516 EditTagsEvent ()
13517 {
13518     char *tags = PGNTags(&gameInfo);
13519     bookUp = FALSE;
13520     EditTagsPopUp(tags, NULL);
13521     free(tags);
13522 }
13523
13524 void
13525 ToggleSecond ()
13526 {
13527   if(second.analyzing) {
13528     SendToProgram("exit\n", &second);
13529     second.analyzing = FALSE;
13530   } else {
13531     if (second.pr == NoProc) StartChessProgram(&second);
13532     InitChessProgram(&second, FALSE);
13533     FeedMovesToProgram(&second, currentMove);
13534
13535     SendToProgram("analyze\n", &second);
13536     second.analyzing = TRUE;
13537   }
13538 }
13539
13540 /* Toggle ShowThinking */
13541 void
13542 ToggleShowThinking()
13543 {
13544   appData.showThinking = !appData.showThinking;
13545   ShowThinkingEvent();
13546 }
13547
13548 int
13549 AnalyzeModeEvent ()
13550 {
13551     char buf[MSG_SIZ];
13552
13553     if (!first.analysisSupport) {
13554       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13555       DisplayError(buf, 0);
13556       return 0;
13557     }
13558     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13559     if (appData.icsActive) {
13560         if (gameMode != IcsObserving) {
13561           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13562             DisplayError(buf, 0);
13563             /* secure check */
13564             if (appData.icsEngineAnalyze) {
13565                 if (appData.debugMode)
13566                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13567                 ExitAnalyzeMode();
13568                 ModeHighlight();
13569             }
13570             return 0;
13571         }
13572         /* if enable, user wants to disable icsEngineAnalyze */
13573         if (appData.icsEngineAnalyze) {
13574                 ExitAnalyzeMode();
13575                 ModeHighlight();
13576                 return 0;
13577         }
13578         appData.icsEngineAnalyze = TRUE;
13579         if (appData.debugMode)
13580             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13581     }
13582
13583     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13584     if (appData.noChessProgram || gameMode == AnalyzeMode)
13585       return 0;
13586
13587     if (gameMode != AnalyzeFile) {
13588         if (!appData.icsEngineAnalyze) {
13589                EditGameEvent();
13590                if (gameMode != EditGame) return 0;
13591         }
13592         if (!appData.showThinking) ToggleShowThinking();
13593         ResurrectChessProgram();
13594         SendToProgram("analyze\n", &first);
13595         first.analyzing = TRUE;
13596         /*first.maybeThinking = TRUE;*/
13597         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13598         EngineOutputPopUp();
13599     }
13600     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13601     pausing = FALSE;
13602     ModeHighlight();
13603     SetGameInfo();
13604
13605     StartAnalysisClock();
13606     GetTimeMark(&lastNodeCountTime);
13607     lastNodeCount = 0;
13608     return 1;
13609 }
13610
13611 void
13612 AnalyzeFileEvent ()
13613 {
13614     if (appData.noChessProgram || gameMode == AnalyzeFile)
13615       return;
13616
13617     if (!first.analysisSupport) {
13618       char buf[MSG_SIZ];
13619       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13620       DisplayError(buf, 0);
13621       return;
13622     }
13623
13624     if (gameMode != AnalyzeMode) {
13625         keepInfo = 1; // mere annotating should not alter PGN tags
13626         EditGameEvent();
13627         keepInfo = 0;
13628         if (gameMode != EditGame) return;
13629         if (!appData.showThinking) ToggleShowThinking();
13630         ResurrectChessProgram();
13631         SendToProgram("analyze\n", &first);
13632         first.analyzing = TRUE;
13633         /*first.maybeThinking = TRUE;*/
13634         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13635         EngineOutputPopUp();
13636     }
13637     gameMode = AnalyzeFile;
13638     pausing = FALSE;
13639     ModeHighlight();
13640
13641     StartAnalysisClock();
13642     GetTimeMark(&lastNodeCountTime);
13643     lastNodeCount = 0;
13644     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13645     AnalysisPeriodicEvent(1);
13646 }
13647
13648 void
13649 MachineWhiteEvent ()
13650 {
13651     char buf[MSG_SIZ];
13652     char *bookHit = NULL;
13653
13654     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13655       return;
13656
13657
13658     if (gameMode == PlayFromGameFile ||
13659         gameMode == TwoMachinesPlay  ||
13660         gameMode == Training         ||
13661         gameMode == AnalyzeMode      ||
13662         gameMode == EndOfGame)
13663         EditGameEvent();
13664
13665     if (gameMode == EditPosition)
13666         EditPositionDone(TRUE);
13667
13668     if (!WhiteOnMove(currentMove)) {
13669         DisplayError(_("It is not White's turn"), 0);
13670         return;
13671     }
13672
13673     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13674       ExitAnalyzeMode();
13675
13676     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13677         gameMode == AnalyzeFile)
13678         TruncateGame();
13679
13680     ResurrectChessProgram();    /* in case it isn't running */
13681     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13682         gameMode = MachinePlaysWhite;
13683         ResetClocks();
13684     } else
13685     gameMode = MachinePlaysWhite;
13686     pausing = FALSE;
13687     ModeHighlight();
13688     SetGameInfo();
13689     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13690     DisplayTitle(buf);
13691     if (first.sendName) {
13692       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13693       SendToProgram(buf, &first);
13694     }
13695     if (first.sendTime) {
13696       if (first.useColors) {
13697         SendToProgram("black\n", &first); /*gnu kludge*/
13698       }
13699       SendTimeRemaining(&first, TRUE);
13700     }
13701     if (first.useColors) {
13702       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13703     }
13704     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13705     SetMachineThinkingEnables();
13706     first.maybeThinking = TRUE;
13707     StartClocks();
13708     firstMove = FALSE;
13709
13710     if (appData.autoFlipView && !flipView) {
13711       flipView = !flipView;
13712       DrawPosition(FALSE, NULL);
13713       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13714     }
13715
13716     if(bookHit) { // [HGM] book: simulate book reply
13717         static char bookMove[MSG_SIZ]; // a bit generous?
13718
13719         programStats.nodes = programStats.depth = programStats.time =
13720         programStats.score = programStats.got_only_move = 0;
13721         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13722
13723         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13724         strcat(bookMove, bookHit);
13725         HandleMachineMove(bookMove, &first);
13726     }
13727 }
13728
13729 void
13730 MachineBlackEvent ()
13731 {
13732   char buf[MSG_SIZ];
13733   char *bookHit = NULL;
13734
13735     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13736         return;
13737
13738
13739     if (gameMode == PlayFromGameFile ||
13740         gameMode == TwoMachinesPlay  ||
13741         gameMode == Training         ||
13742         gameMode == AnalyzeMode      ||
13743         gameMode == EndOfGame)
13744         EditGameEvent();
13745
13746     if (gameMode == EditPosition)
13747         EditPositionDone(TRUE);
13748
13749     if (WhiteOnMove(currentMove)) {
13750         DisplayError(_("It is not Black's turn"), 0);
13751         return;
13752     }
13753
13754     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13755       ExitAnalyzeMode();
13756
13757     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13758         gameMode == AnalyzeFile)
13759         TruncateGame();
13760
13761     ResurrectChessProgram();    /* in case it isn't running */
13762     gameMode = MachinePlaysBlack;
13763     pausing = FALSE;
13764     ModeHighlight();
13765     SetGameInfo();
13766     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13767     DisplayTitle(buf);
13768     if (first.sendName) {
13769       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13770       SendToProgram(buf, &first);
13771     }
13772     if (first.sendTime) {
13773       if (first.useColors) {
13774         SendToProgram("white\n", &first); /*gnu kludge*/
13775       }
13776       SendTimeRemaining(&first, FALSE);
13777     }
13778     if (first.useColors) {
13779       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13780     }
13781     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13782     SetMachineThinkingEnables();
13783     first.maybeThinking = TRUE;
13784     StartClocks();
13785
13786     if (appData.autoFlipView && flipView) {
13787       flipView = !flipView;
13788       DrawPosition(FALSE, NULL);
13789       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13790     }
13791     if(bookHit) { // [HGM] book: simulate book reply
13792         static char bookMove[MSG_SIZ]; // a bit generous?
13793
13794         programStats.nodes = programStats.depth = programStats.time =
13795         programStats.score = programStats.got_only_move = 0;
13796         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13797
13798         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13799         strcat(bookMove, bookHit);
13800         HandleMachineMove(bookMove, &first);
13801     }
13802 }
13803
13804
13805 void
13806 DisplayTwoMachinesTitle ()
13807 {
13808     char buf[MSG_SIZ];
13809     if (appData.matchGames > 0) {
13810         if(appData.tourneyFile[0]) {
13811           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13812                    gameInfo.white, _("vs."), gameInfo.black,
13813                    nextGame+1, appData.matchGames+1,
13814                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13815         } else 
13816         if (first.twoMachinesColor[0] == 'w') {
13817           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13818                    gameInfo.white, _("vs."),  gameInfo.black,
13819                    first.matchWins, second.matchWins,
13820                    matchGame - 1 - (first.matchWins + second.matchWins));
13821         } else {
13822           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13823                    gameInfo.white, _("vs."), gameInfo.black,
13824                    second.matchWins, first.matchWins,
13825                    matchGame - 1 - (first.matchWins + second.matchWins));
13826         }
13827     } else {
13828       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13829     }
13830     DisplayTitle(buf);
13831 }
13832
13833 void
13834 SettingsMenuIfReady ()
13835 {
13836   if (second.lastPing != second.lastPong) {
13837     DisplayMessage("", _("Waiting for second chess program"));
13838     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13839     return;
13840   }
13841   ThawUI();
13842   DisplayMessage("", "");
13843   SettingsPopUp(&second);
13844 }
13845
13846 int
13847 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13848 {
13849     char buf[MSG_SIZ];
13850     if (cps->pr == NoProc) {
13851         StartChessProgram(cps);
13852         if (cps->protocolVersion == 1) {
13853           retry();
13854         } else {
13855           /* kludge: allow timeout for initial "feature" command */
13856           FreezeUI();
13857           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13858           DisplayMessage("", buf);
13859           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13860         }
13861         return 1;
13862     }
13863     return 0;
13864 }
13865
13866 void
13867 TwoMachinesEvent P((void))
13868 {
13869     int i;
13870     char buf[MSG_SIZ];
13871     ChessProgramState *onmove;
13872     char *bookHit = NULL;
13873     static int stalling = 0;
13874     TimeMark now;
13875     long wait;
13876
13877     if (appData.noChessProgram) return;
13878
13879     switch (gameMode) {
13880       case TwoMachinesPlay:
13881         return;
13882       case MachinePlaysWhite:
13883       case MachinePlaysBlack:
13884         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13885             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13886             return;
13887         }
13888         /* fall through */
13889       case BeginningOfGame:
13890       case PlayFromGameFile:
13891       case EndOfGame:
13892         EditGameEvent();
13893         if (gameMode != EditGame) return;
13894         break;
13895       case EditPosition:
13896         EditPositionDone(TRUE);
13897         break;
13898       case AnalyzeMode:
13899       case AnalyzeFile:
13900         ExitAnalyzeMode();
13901         break;
13902       case EditGame:
13903       default:
13904         break;
13905     }
13906
13907 //    forwardMostMove = currentMove;
13908     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13909
13910     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13911
13912     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13913     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13914       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13915       return;
13916     }
13917
13918     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13919         DisplayError("second engine does not play this", 0);
13920         return;
13921     }
13922
13923     if(!stalling) {
13924       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13925       SendToProgram("force\n", &second);
13926       stalling = 1;
13927       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13928       return;
13929     }
13930     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13931     if(appData.matchPause>10000 || appData.matchPause<10)
13932                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13933     wait = SubtractTimeMarks(&now, &pauseStart);
13934     if(wait < appData.matchPause) {
13935         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13936         return;
13937     }
13938     // we are now committed to starting the game
13939     stalling = 0;
13940     DisplayMessage("", "");
13941     if (startedFromSetupPosition) {
13942         SendBoard(&second, backwardMostMove);
13943     if (appData.debugMode) {
13944         fprintf(debugFP, "Two Machines\n");
13945     }
13946     }
13947     for (i = backwardMostMove; i < forwardMostMove; i++) {
13948         SendMoveToProgram(i, &second);
13949     }
13950
13951     gameMode = TwoMachinesPlay;
13952     pausing = FALSE;
13953     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13954     SetGameInfo();
13955     DisplayTwoMachinesTitle();
13956     firstMove = TRUE;
13957     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13958         onmove = &first;
13959     } else {
13960         onmove = &second;
13961     }
13962     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13963     SendToProgram(first.computerString, &first);
13964     if (first.sendName) {
13965       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13966       SendToProgram(buf, &first);
13967     }
13968     SendToProgram(second.computerString, &second);
13969     if (second.sendName) {
13970       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13971       SendToProgram(buf, &second);
13972     }
13973
13974     ResetClocks();
13975     if (!first.sendTime || !second.sendTime) {
13976         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13977         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13978     }
13979     if (onmove->sendTime) {
13980       if (onmove->useColors) {
13981         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13982       }
13983       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13984     }
13985     if (onmove->useColors) {
13986       SendToProgram(onmove->twoMachinesColor, onmove);
13987     }
13988     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13989 //    SendToProgram("go\n", onmove);
13990     onmove->maybeThinking = TRUE;
13991     SetMachineThinkingEnables();
13992
13993     StartClocks();
13994
13995     if(bookHit) { // [HGM] book: simulate book reply
13996         static char bookMove[MSG_SIZ]; // a bit generous?
13997
13998         programStats.nodes = programStats.depth = programStats.time =
13999         programStats.score = programStats.got_only_move = 0;
14000         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14001
14002         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14003         strcat(bookMove, bookHit);
14004         savedMessage = bookMove; // args for deferred call
14005         savedState = onmove;
14006         ScheduleDelayedEvent(DeferredBookMove, 1);
14007     }
14008 }
14009
14010 void
14011 TrainingEvent ()
14012 {
14013     if (gameMode == Training) {
14014       SetTrainingModeOff();
14015       gameMode = PlayFromGameFile;
14016       DisplayMessage("", _("Training mode off"));
14017     } else {
14018       gameMode = Training;
14019       animateTraining = appData.animate;
14020
14021       /* make sure we are not already at the end of the game */
14022       if (currentMove < forwardMostMove) {
14023         SetTrainingModeOn();
14024         DisplayMessage("", _("Training mode on"));
14025       } else {
14026         gameMode = PlayFromGameFile;
14027         DisplayError(_("Already at end of game"), 0);
14028       }
14029     }
14030     ModeHighlight();
14031 }
14032
14033 void
14034 IcsClientEvent ()
14035 {
14036     if (!appData.icsActive) return;
14037     switch (gameMode) {
14038       case IcsPlayingWhite:
14039       case IcsPlayingBlack:
14040       case IcsObserving:
14041       case IcsIdle:
14042       case BeginningOfGame:
14043       case IcsExamining:
14044         return;
14045
14046       case EditGame:
14047         break;
14048
14049       case EditPosition:
14050         EditPositionDone(TRUE);
14051         break;
14052
14053       case AnalyzeMode:
14054       case AnalyzeFile:
14055         ExitAnalyzeMode();
14056         break;
14057
14058       default:
14059         EditGameEvent();
14060         break;
14061     }
14062
14063     gameMode = IcsIdle;
14064     ModeHighlight();
14065     return;
14066 }
14067
14068 void
14069 EditGameEvent ()
14070 {
14071     int i;
14072
14073     switch (gameMode) {
14074       case Training:
14075         SetTrainingModeOff();
14076         break;
14077       case MachinePlaysWhite:
14078       case MachinePlaysBlack:
14079       case BeginningOfGame:
14080         SendToProgram("force\n", &first);
14081         SetUserThinkingEnables();
14082         break;
14083       case PlayFromGameFile:
14084         (void) StopLoadGameTimer();
14085         if (gameFileFP != NULL) {
14086             gameFileFP = NULL;
14087         }
14088         break;
14089       case EditPosition:
14090         EditPositionDone(TRUE);
14091         break;
14092       case AnalyzeMode:
14093       case AnalyzeFile:
14094         ExitAnalyzeMode();
14095         SendToProgram("force\n", &first);
14096         break;
14097       case TwoMachinesPlay:
14098         GameEnds(EndOfFile, NULL, GE_PLAYER);
14099         ResurrectChessProgram();
14100         SetUserThinkingEnables();
14101         break;
14102       case EndOfGame:
14103         ResurrectChessProgram();
14104         break;
14105       case IcsPlayingBlack:
14106       case IcsPlayingWhite:
14107         DisplayError(_("Warning: You are still playing a game"), 0);
14108         break;
14109       case IcsObserving:
14110         DisplayError(_("Warning: You are still observing a game"), 0);
14111         break;
14112       case IcsExamining:
14113         DisplayError(_("Warning: You are still examining a game"), 0);
14114         break;
14115       case IcsIdle:
14116         break;
14117       case EditGame:
14118       default:
14119         return;
14120     }
14121
14122     pausing = FALSE;
14123     StopClocks();
14124     first.offeredDraw = second.offeredDraw = 0;
14125
14126     if (gameMode == PlayFromGameFile) {
14127         whiteTimeRemaining = timeRemaining[0][currentMove];
14128         blackTimeRemaining = timeRemaining[1][currentMove];
14129         DisplayTitle("");
14130     }
14131
14132     if (gameMode == MachinePlaysWhite ||
14133         gameMode == MachinePlaysBlack ||
14134         gameMode == TwoMachinesPlay ||
14135         gameMode == EndOfGame) {
14136         i = forwardMostMove;
14137         while (i > currentMove) {
14138             SendToProgram("undo\n", &first);
14139             i--;
14140         }
14141         if(!adjustedClock) {
14142         whiteTimeRemaining = timeRemaining[0][currentMove];
14143         blackTimeRemaining = timeRemaining[1][currentMove];
14144         DisplayBothClocks();
14145         }
14146         if (whiteFlag || blackFlag) {
14147             whiteFlag = blackFlag = 0;
14148         }
14149         DisplayTitle("");
14150     }
14151
14152     gameMode = EditGame;
14153     ModeHighlight();
14154     SetGameInfo();
14155 }
14156
14157
14158 void
14159 EditPositionEvent ()
14160 {
14161     if (gameMode == EditPosition) {
14162         EditGameEvent();
14163         return;
14164     }
14165
14166     EditGameEvent();
14167     if (gameMode != EditGame) return;
14168
14169     gameMode = EditPosition;
14170     ModeHighlight();
14171     SetGameInfo();
14172     if (currentMove > 0)
14173       CopyBoard(boards[0], boards[currentMove]);
14174
14175     blackPlaysFirst = !WhiteOnMove(currentMove);
14176     ResetClocks();
14177     currentMove = forwardMostMove = backwardMostMove = 0;
14178     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14179     DisplayMove(-1);
14180     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14181 }
14182
14183 void
14184 ExitAnalyzeMode ()
14185 {
14186     /* [DM] icsEngineAnalyze - possible call from other functions */
14187     if (appData.icsEngineAnalyze) {
14188         appData.icsEngineAnalyze = FALSE;
14189
14190         DisplayMessage("",_("Close ICS engine analyze..."));
14191     }
14192     if (first.analysisSupport && first.analyzing) {
14193       SendToBoth("exit\n");
14194       first.analyzing = second.analyzing = FALSE;
14195     }
14196     thinkOutput[0] = NULLCHAR;
14197 }
14198
14199 void
14200 EditPositionDone (Boolean fakeRights)
14201 {
14202     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14203
14204     startedFromSetupPosition = TRUE;
14205     InitChessProgram(&first, FALSE);
14206     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14207       boards[0][EP_STATUS] = EP_NONE;
14208       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14209       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14210         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14211         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14212       } else boards[0][CASTLING][2] = NoRights;
14213       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14214         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14215         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14216       } else boards[0][CASTLING][5] = NoRights;
14217       if(gameInfo.variant == VariantSChess) {
14218         int i;
14219         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14220           boards[0][VIRGIN][i] = 0;
14221           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14222           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14223         }
14224       }
14225     }
14226     SendToProgram("force\n", &first);
14227     if (blackPlaysFirst) {
14228         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14229         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14230         currentMove = forwardMostMove = backwardMostMove = 1;
14231         CopyBoard(boards[1], boards[0]);
14232     } else {
14233         currentMove = forwardMostMove = backwardMostMove = 0;
14234     }
14235     SendBoard(&first, forwardMostMove);
14236     if (appData.debugMode) {
14237         fprintf(debugFP, "EditPosDone\n");
14238     }
14239     DisplayTitle("");
14240     DisplayMessage("", "");
14241     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14242     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14243     gameMode = EditGame;
14244     ModeHighlight();
14245     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14246     ClearHighlights(); /* [AS] */
14247 }
14248
14249 /* Pause for `ms' milliseconds */
14250 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14251 void
14252 TimeDelay (long ms)
14253 {
14254     TimeMark m1, m2;
14255
14256     GetTimeMark(&m1);
14257     do {
14258         GetTimeMark(&m2);
14259     } while (SubtractTimeMarks(&m2, &m1) < ms);
14260 }
14261
14262 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14263 void
14264 SendMultiLineToICS (char *buf)
14265 {
14266     char temp[MSG_SIZ+1], *p;
14267     int len;
14268
14269     len = strlen(buf);
14270     if (len > MSG_SIZ)
14271       len = MSG_SIZ;
14272
14273     strncpy(temp, buf, len);
14274     temp[len] = 0;
14275
14276     p = temp;
14277     while (*p) {
14278         if (*p == '\n' || *p == '\r')
14279           *p = ' ';
14280         ++p;
14281     }
14282
14283     strcat(temp, "\n");
14284     SendToICS(temp);
14285     SendToPlayer(temp, strlen(temp));
14286 }
14287
14288 void
14289 SetWhiteToPlayEvent ()
14290 {
14291     if (gameMode == EditPosition) {
14292         blackPlaysFirst = FALSE;
14293         DisplayBothClocks();    /* works because currentMove is 0 */
14294     } else if (gameMode == IcsExamining) {
14295         SendToICS(ics_prefix);
14296         SendToICS("tomove white\n");
14297     }
14298 }
14299
14300 void
14301 SetBlackToPlayEvent ()
14302 {
14303     if (gameMode == EditPosition) {
14304         blackPlaysFirst = TRUE;
14305         currentMove = 1;        /* kludge */
14306         DisplayBothClocks();
14307         currentMove = 0;
14308     } else if (gameMode == IcsExamining) {
14309         SendToICS(ics_prefix);
14310         SendToICS("tomove black\n");
14311     }
14312 }
14313
14314 void
14315 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14316 {
14317     char buf[MSG_SIZ];
14318     ChessSquare piece = boards[0][y][x];
14319
14320     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14321
14322     switch (selection) {
14323       case ClearBoard:
14324         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14325             SendToICS(ics_prefix);
14326             SendToICS("bsetup clear\n");
14327         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14328             SendToICS(ics_prefix);
14329             SendToICS("clearboard\n");
14330         } else {
14331             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14332                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14333                 for (y = 0; y < BOARD_HEIGHT; y++) {
14334                     if (gameMode == IcsExamining) {
14335                         if (boards[currentMove][y][x] != EmptySquare) {
14336                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14337                                     AAA + x, ONE + y);
14338                             SendToICS(buf);
14339                         }
14340                     } else {
14341                         boards[0][y][x] = p;
14342                     }
14343                 }
14344             }
14345         }
14346         if (gameMode == EditPosition) {
14347             DrawPosition(FALSE, boards[0]);
14348         }
14349         break;
14350
14351       case WhitePlay:
14352         SetWhiteToPlayEvent();
14353         break;
14354
14355       case BlackPlay:
14356         SetBlackToPlayEvent();
14357         break;
14358
14359       case EmptySquare:
14360         if (gameMode == IcsExamining) {
14361             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14362             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14363             SendToICS(buf);
14364         } else {
14365             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14366                 if(x == BOARD_LEFT-2) {
14367                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14368                     boards[0][y][1] = 0;
14369                 } else
14370                 if(x == BOARD_RGHT+1) {
14371                     if(y >= gameInfo.holdingsSize) break;
14372                     boards[0][y][BOARD_WIDTH-2] = 0;
14373                 } else break;
14374             }
14375             boards[0][y][x] = EmptySquare;
14376             DrawPosition(FALSE, boards[0]);
14377         }
14378         break;
14379
14380       case PromotePiece:
14381         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14382            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14383             selection = (ChessSquare) (PROMOTED piece);
14384         } else if(piece == EmptySquare) selection = WhiteSilver;
14385         else selection = (ChessSquare)((int)piece - 1);
14386         goto defaultlabel;
14387
14388       case DemotePiece:
14389         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14390            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14391             selection = (ChessSquare) (DEMOTED piece);
14392         } else if(piece == EmptySquare) selection = BlackSilver;
14393         else selection = (ChessSquare)((int)piece + 1);
14394         goto defaultlabel;
14395
14396       case WhiteQueen:
14397       case BlackQueen:
14398         if(gameInfo.variant == VariantShatranj ||
14399            gameInfo.variant == VariantXiangqi  ||
14400            gameInfo.variant == VariantCourier  ||
14401            gameInfo.variant == VariantMakruk     )
14402             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14403         goto defaultlabel;
14404
14405       case WhiteKing:
14406       case BlackKing:
14407         if(gameInfo.variant == VariantXiangqi)
14408             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14409         if(gameInfo.variant == VariantKnightmate)
14410             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14411       default:
14412         defaultlabel:
14413         if (gameMode == IcsExamining) {
14414             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14415             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14416                      PieceToChar(selection), AAA + x, ONE + y);
14417             SendToICS(buf);
14418         } else {
14419             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14420                 int n;
14421                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14422                     n = PieceToNumber(selection - BlackPawn);
14423                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14424                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14425                     boards[0][BOARD_HEIGHT-1-n][1]++;
14426                 } else
14427                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14428                     n = PieceToNumber(selection);
14429                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14430                     boards[0][n][BOARD_WIDTH-1] = selection;
14431                     boards[0][n][BOARD_WIDTH-2]++;
14432                 }
14433             } else
14434             boards[0][y][x] = selection;
14435             DrawPosition(TRUE, boards[0]);
14436             ClearHighlights();
14437             fromX = fromY = -1;
14438         }
14439         break;
14440     }
14441 }
14442
14443
14444 void
14445 DropMenuEvent (ChessSquare selection, int x, int y)
14446 {
14447     ChessMove moveType;
14448
14449     switch (gameMode) {
14450       case IcsPlayingWhite:
14451       case MachinePlaysBlack:
14452         if (!WhiteOnMove(currentMove)) {
14453             DisplayMoveError(_("It is Black's turn"));
14454             return;
14455         }
14456         moveType = WhiteDrop;
14457         break;
14458       case IcsPlayingBlack:
14459       case MachinePlaysWhite:
14460         if (WhiteOnMove(currentMove)) {
14461             DisplayMoveError(_("It is White's turn"));
14462             return;
14463         }
14464         moveType = BlackDrop;
14465         break;
14466       case EditGame:
14467         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14468         break;
14469       default:
14470         return;
14471     }
14472
14473     if (moveType == BlackDrop && selection < BlackPawn) {
14474       selection = (ChessSquare) ((int) selection
14475                                  + (int) BlackPawn - (int) WhitePawn);
14476     }
14477     if (boards[currentMove][y][x] != EmptySquare) {
14478         DisplayMoveError(_("That square is occupied"));
14479         return;
14480     }
14481
14482     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14483 }
14484
14485 void
14486 AcceptEvent ()
14487 {
14488     /* Accept a pending offer of any kind from opponent */
14489
14490     if (appData.icsActive) {
14491         SendToICS(ics_prefix);
14492         SendToICS("accept\n");
14493     } else if (cmailMsgLoaded) {
14494         if (currentMove == cmailOldMove &&
14495             commentList[cmailOldMove] != NULL &&
14496             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14497                    "Black offers a draw" : "White offers a draw")) {
14498             TruncateGame();
14499             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14500             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14501         } else {
14502             DisplayError(_("There is no pending offer on this move"), 0);
14503             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14504         }
14505     } else {
14506         /* Not used for offers from chess program */
14507     }
14508 }
14509
14510 void
14511 DeclineEvent ()
14512 {
14513     /* Decline a pending offer of any kind from opponent */
14514
14515     if (appData.icsActive) {
14516         SendToICS(ics_prefix);
14517         SendToICS("decline\n");
14518     } else if (cmailMsgLoaded) {
14519         if (currentMove == cmailOldMove &&
14520             commentList[cmailOldMove] != NULL &&
14521             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14522                    "Black offers a draw" : "White offers a draw")) {
14523 #ifdef NOTDEF
14524             AppendComment(cmailOldMove, "Draw declined", TRUE);
14525             DisplayComment(cmailOldMove - 1, "Draw declined");
14526 #endif /*NOTDEF*/
14527         } else {
14528             DisplayError(_("There is no pending offer on this move"), 0);
14529         }
14530     } else {
14531         /* Not used for offers from chess program */
14532     }
14533 }
14534
14535 void
14536 RematchEvent ()
14537 {
14538     /* Issue ICS rematch command */
14539     if (appData.icsActive) {
14540         SendToICS(ics_prefix);
14541         SendToICS("rematch\n");
14542     }
14543 }
14544
14545 void
14546 CallFlagEvent ()
14547 {
14548     /* Call your opponent's flag (claim a win on time) */
14549     if (appData.icsActive) {
14550         SendToICS(ics_prefix);
14551         SendToICS("flag\n");
14552     } else {
14553         switch (gameMode) {
14554           default:
14555             return;
14556           case MachinePlaysWhite:
14557             if (whiteFlag) {
14558                 if (blackFlag)
14559                   GameEnds(GameIsDrawn, "Both players ran out of time",
14560                            GE_PLAYER);
14561                 else
14562                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14563             } else {
14564                 DisplayError(_("Your opponent is not out of time"), 0);
14565             }
14566             break;
14567           case MachinePlaysBlack:
14568             if (blackFlag) {
14569                 if (whiteFlag)
14570                   GameEnds(GameIsDrawn, "Both players ran out of time",
14571                            GE_PLAYER);
14572                 else
14573                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14574             } else {
14575                 DisplayError(_("Your opponent is not out of time"), 0);
14576             }
14577             break;
14578         }
14579     }
14580 }
14581
14582 void
14583 ClockClick (int which)
14584 {       // [HGM] code moved to back-end from winboard.c
14585         if(which) { // black clock
14586           if (gameMode == EditPosition || gameMode == IcsExamining) {
14587             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14588             SetBlackToPlayEvent();
14589           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14590           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14591           } else if (shiftKey) {
14592             AdjustClock(which, -1);
14593           } else if (gameMode == IcsPlayingWhite ||
14594                      gameMode == MachinePlaysBlack) {
14595             CallFlagEvent();
14596           }
14597         } else { // white clock
14598           if (gameMode == EditPosition || gameMode == IcsExamining) {
14599             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14600             SetWhiteToPlayEvent();
14601           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14602           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14603           } else if (shiftKey) {
14604             AdjustClock(which, -1);
14605           } else if (gameMode == IcsPlayingBlack ||
14606                    gameMode == MachinePlaysWhite) {
14607             CallFlagEvent();
14608           }
14609         }
14610 }
14611
14612 void
14613 DrawEvent ()
14614 {
14615     /* Offer draw or accept pending draw offer from opponent */
14616
14617     if (appData.icsActive) {
14618         /* Note: tournament rules require draw offers to be
14619            made after you make your move but before you punch
14620            your clock.  Currently ICS doesn't let you do that;
14621            instead, you immediately punch your clock after making
14622            a move, but you can offer a draw at any time. */
14623
14624         SendToICS(ics_prefix);
14625         SendToICS("draw\n");
14626         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14627     } else if (cmailMsgLoaded) {
14628         if (currentMove == cmailOldMove &&
14629             commentList[cmailOldMove] != NULL &&
14630             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14631                    "Black offers a draw" : "White offers a draw")) {
14632             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14633             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14634         } else if (currentMove == cmailOldMove + 1) {
14635             char *offer = WhiteOnMove(cmailOldMove) ?
14636               "White offers a draw" : "Black offers a draw";
14637             AppendComment(currentMove, offer, TRUE);
14638             DisplayComment(currentMove - 1, offer);
14639             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14640         } else {
14641             DisplayError(_("You must make your move before offering a draw"), 0);
14642             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14643         }
14644     } else if (first.offeredDraw) {
14645         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14646     } else {
14647         if (first.sendDrawOffers) {
14648             SendToProgram("draw\n", &first);
14649             userOfferedDraw = TRUE;
14650         }
14651     }
14652 }
14653
14654 void
14655 AdjournEvent ()
14656 {
14657     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14658
14659     if (appData.icsActive) {
14660         SendToICS(ics_prefix);
14661         SendToICS("adjourn\n");
14662     } else {
14663         /* Currently GNU Chess doesn't offer or accept Adjourns */
14664     }
14665 }
14666
14667
14668 void
14669 AbortEvent ()
14670 {
14671     /* Offer Abort or accept pending Abort offer from opponent */
14672
14673     if (appData.icsActive) {
14674         SendToICS(ics_prefix);
14675         SendToICS("abort\n");
14676     } else {
14677         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14678     }
14679 }
14680
14681 void
14682 ResignEvent ()
14683 {
14684     /* Resign.  You can do this even if it's not your turn. */
14685
14686     if (appData.icsActive) {
14687         SendToICS(ics_prefix);
14688         SendToICS("resign\n");
14689     } else {
14690         switch (gameMode) {
14691           case MachinePlaysWhite:
14692             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14693             break;
14694           case MachinePlaysBlack:
14695             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14696             break;
14697           case EditGame:
14698             if (cmailMsgLoaded) {
14699                 TruncateGame();
14700                 if (WhiteOnMove(cmailOldMove)) {
14701                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14702                 } else {
14703                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14704                 }
14705                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14706             }
14707             break;
14708           default:
14709             break;
14710         }
14711     }
14712 }
14713
14714
14715 void
14716 StopObservingEvent ()
14717 {
14718     /* Stop observing current games */
14719     SendToICS(ics_prefix);
14720     SendToICS("unobserve\n");
14721 }
14722
14723 void
14724 StopExaminingEvent ()
14725 {
14726     /* Stop observing current game */
14727     SendToICS(ics_prefix);
14728     SendToICS("unexamine\n");
14729 }
14730
14731 void
14732 ForwardInner (int target)
14733 {
14734     int limit; int oldSeekGraphUp = seekGraphUp;
14735
14736     if (appData.debugMode)
14737         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14738                 target, currentMove, forwardMostMove);
14739
14740     if (gameMode == EditPosition)
14741       return;
14742
14743     seekGraphUp = FALSE;
14744     MarkTargetSquares(1);
14745
14746     if (gameMode == PlayFromGameFile && !pausing)
14747       PauseEvent();
14748
14749     if (gameMode == IcsExamining && pausing)
14750       limit = pauseExamForwardMostMove;
14751     else
14752       limit = forwardMostMove;
14753
14754     if (target > limit) target = limit;
14755
14756     if (target > 0 && moveList[target - 1][0]) {
14757         int fromX, fromY, toX, toY;
14758         toX = moveList[target - 1][2] - AAA;
14759         toY = moveList[target - 1][3] - ONE;
14760         if (moveList[target - 1][1] == '@') {
14761             if (appData.highlightLastMove) {
14762                 SetHighlights(-1, -1, toX, toY);
14763             }
14764         } else {
14765             fromX = moveList[target - 1][0] - AAA;
14766             fromY = moveList[target - 1][1] - ONE;
14767             if (target == currentMove + 1) {
14768                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14769             }
14770             if (appData.highlightLastMove) {
14771                 SetHighlights(fromX, fromY, toX, toY);
14772             }
14773         }
14774     }
14775     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14776         gameMode == Training || gameMode == PlayFromGameFile ||
14777         gameMode == AnalyzeFile) {
14778         while (currentMove < target) {
14779             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14780             SendMoveToProgram(currentMove++, &first);
14781         }
14782     } else {
14783         currentMove = target;
14784     }
14785
14786     if (gameMode == EditGame || gameMode == EndOfGame) {
14787         whiteTimeRemaining = timeRemaining[0][currentMove];
14788         blackTimeRemaining = timeRemaining[1][currentMove];
14789     }
14790     DisplayBothClocks();
14791     DisplayMove(currentMove - 1);
14792     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14793     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14794     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14795         DisplayComment(currentMove - 1, commentList[currentMove]);
14796     }
14797     ClearMap(); // [HGM] exclude: invalidate map
14798 }
14799
14800
14801 void
14802 ForwardEvent ()
14803 {
14804     if (gameMode == IcsExamining && !pausing) {
14805         SendToICS(ics_prefix);
14806         SendToICS("forward\n");
14807     } else {
14808         ForwardInner(currentMove + 1);
14809     }
14810 }
14811
14812 void
14813 ToEndEvent ()
14814 {
14815     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14816         /* to optimze, we temporarily turn off analysis mode while we feed
14817          * the remaining moves to the engine. Otherwise we get analysis output
14818          * after each move.
14819          */
14820         if (first.analysisSupport) {
14821           SendToProgram("exit\nforce\n", &first);
14822           first.analyzing = FALSE;
14823         }
14824     }
14825
14826     if (gameMode == IcsExamining && !pausing) {
14827         SendToICS(ics_prefix);
14828         SendToICS("forward 999999\n");
14829     } else {
14830         ForwardInner(forwardMostMove);
14831     }
14832
14833     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14834         /* we have fed all the moves, so reactivate analysis mode */
14835         SendToProgram("analyze\n", &first);
14836         first.analyzing = TRUE;
14837         /*first.maybeThinking = TRUE;*/
14838         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14839     }
14840 }
14841
14842 void
14843 BackwardInner (int target)
14844 {
14845     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14846
14847     if (appData.debugMode)
14848         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14849                 target, currentMove, forwardMostMove);
14850
14851     if (gameMode == EditPosition) return;
14852     seekGraphUp = FALSE;
14853     MarkTargetSquares(1);
14854     if (currentMove <= backwardMostMove) {
14855         ClearHighlights();
14856         DrawPosition(full_redraw, boards[currentMove]);
14857         return;
14858     }
14859     if (gameMode == PlayFromGameFile && !pausing)
14860       PauseEvent();
14861
14862     if (moveList[target][0]) {
14863         int fromX, fromY, toX, toY;
14864         toX = moveList[target][2] - AAA;
14865         toY = moveList[target][3] - ONE;
14866         if (moveList[target][1] == '@') {
14867             if (appData.highlightLastMove) {
14868                 SetHighlights(-1, -1, toX, toY);
14869             }
14870         } else {
14871             fromX = moveList[target][0] - AAA;
14872             fromY = moveList[target][1] - ONE;
14873             if (target == currentMove - 1) {
14874                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14875             }
14876             if (appData.highlightLastMove) {
14877                 SetHighlights(fromX, fromY, toX, toY);
14878             }
14879         }
14880     }
14881     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14882         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14883         while (currentMove > target) {
14884             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14885                 // null move cannot be undone. Reload program with move history before it.
14886                 int i;
14887                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14888                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14889                 }
14890                 SendBoard(&first, i); 
14891               if(second.analyzing) SendBoard(&second, i);
14892                 for(currentMove=i; currentMove<target; currentMove++) {
14893                     SendMoveToProgram(currentMove, &first);
14894                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14895                 }
14896                 break;
14897             }
14898             SendToBoth("undo\n");
14899             currentMove--;
14900         }
14901     } else {
14902         currentMove = target;
14903     }
14904
14905     if (gameMode == EditGame || gameMode == EndOfGame) {
14906         whiteTimeRemaining = timeRemaining[0][currentMove];
14907         blackTimeRemaining = timeRemaining[1][currentMove];
14908     }
14909     DisplayBothClocks();
14910     DisplayMove(currentMove - 1);
14911     DrawPosition(full_redraw, boards[currentMove]);
14912     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14913     // [HGM] PV info: routine tests if comment empty
14914     DisplayComment(currentMove - 1, commentList[currentMove]);
14915     ClearMap(); // [HGM] exclude: invalidate map
14916 }
14917
14918 void
14919 BackwardEvent ()
14920 {
14921     if (gameMode == IcsExamining && !pausing) {
14922         SendToICS(ics_prefix);
14923         SendToICS("backward\n");
14924     } else {
14925         BackwardInner(currentMove - 1);
14926     }
14927 }
14928
14929 void
14930 ToStartEvent ()
14931 {
14932     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14933         /* to optimize, we temporarily turn off analysis mode while we undo
14934          * all the moves. Otherwise we get analysis output after each undo.
14935          */
14936         if (first.analysisSupport) {
14937           SendToProgram("exit\nforce\n", &first);
14938           first.analyzing = FALSE;
14939         }
14940     }
14941
14942     if (gameMode == IcsExamining && !pausing) {
14943         SendToICS(ics_prefix);
14944         SendToICS("backward 999999\n");
14945     } else {
14946         BackwardInner(backwardMostMove);
14947     }
14948
14949     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14950         /* we have fed all the moves, so reactivate analysis mode */
14951         SendToProgram("analyze\n", &first);
14952         first.analyzing = TRUE;
14953         /*first.maybeThinking = TRUE;*/
14954         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14955     }
14956 }
14957
14958 void
14959 ToNrEvent (int to)
14960 {
14961   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14962   if (to >= forwardMostMove) to = forwardMostMove;
14963   if (to <= backwardMostMove) to = backwardMostMove;
14964   if (to < currentMove) {
14965     BackwardInner(to);
14966   } else {
14967     ForwardInner(to);
14968   }
14969 }
14970
14971 void
14972 RevertEvent (Boolean annotate)
14973 {
14974     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14975         return;
14976     }
14977     if (gameMode != IcsExamining) {
14978         DisplayError(_("You are not examining a game"), 0);
14979         return;
14980     }
14981     if (pausing) {
14982         DisplayError(_("You can't revert while pausing"), 0);
14983         return;
14984     }
14985     SendToICS(ics_prefix);
14986     SendToICS("revert\n");
14987 }
14988
14989 void
14990 RetractMoveEvent ()
14991 {
14992     switch (gameMode) {
14993       case MachinePlaysWhite:
14994       case MachinePlaysBlack:
14995         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14996             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14997             return;
14998         }
14999         if (forwardMostMove < 2) return;
15000         currentMove = forwardMostMove = forwardMostMove - 2;
15001         whiteTimeRemaining = timeRemaining[0][currentMove];
15002         blackTimeRemaining = timeRemaining[1][currentMove];
15003         DisplayBothClocks();
15004         DisplayMove(currentMove - 1);
15005         ClearHighlights();/*!! could figure this out*/
15006         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15007         SendToProgram("remove\n", &first);
15008         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15009         break;
15010
15011       case BeginningOfGame:
15012       default:
15013         break;
15014
15015       case IcsPlayingWhite:
15016       case IcsPlayingBlack:
15017         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15018             SendToICS(ics_prefix);
15019             SendToICS("takeback 2\n");
15020         } else {
15021             SendToICS(ics_prefix);
15022             SendToICS("takeback 1\n");
15023         }
15024         break;
15025     }
15026 }
15027
15028 void
15029 MoveNowEvent ()
15030 {
15031     ChessProgramState *cps;
15032
15033     switch (gameMode) {
15034       case MachinePlaysWhite:
15035         if (!WhiteOnMove(forwardMostMove)) {
15036             DisplayError(_("It is your turn"), 0);
15037             return;
15038         }
15039         cps = &first;
15040         break;
15041       case MachinePlaysBlack:
15042         if (WhiteOnMove(forwardMostMove)) {
15043             DisplayError(_("It is your turn"), 0);
15044             return;
15045         }
15046         cps = &first;
15047         break;
15048       case TwoMachinesPlay:
15049         if (WhiteOnMove(forwardMostMove) ==
15050             (first.twoMachinesColor[0] == 'w')) {
15051             cps = &first;
15052         } else {
15053             cps = &second;
15054         }
15055         break;
15056       case BeginningOfGame:
15057       default:
15058         return;
15059     }
15060     SendToProgram("?\n", cps);
15061 }
15062
15063 void
15064 TruncateGameEvent ()
15065 {
15066     EditGameEvent();
15067     if (gameMode != EditGame) return;
15068     TruncateGame();
15069 }
15070
15071 void
15072 TruncateGame ()
15073 {
15074     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15075     if (forwardMostMove > currentMove) {
15076         if (gameInfo.resultDetails != NULL) {
15077             free(gameInfo.resultDetails);
15078             gameInfo.resultDetails = NULL;
15079             gameInfo.result = GameUnfinished;
15080         }
15081         forwardMostMove = currentMove;
15082         HistorySet(parseList, backwardMostMove, forwardMostMove,
15083                    currentMove-1);
15084     }
15085 }
15086
15087 void
15088 HintEvent ()
15089 {
15090     if (appData.noChessProgram) return;
15091     switch (gameMode) {
15092       case MachinePlaysWhite:
15093         if (WhiteOnMove(forwardMostMove)) {
15094             DisplayError(_("Wait until your turn"), 0);
15095             return;
15096         }
15097         break;
15098       case BeginningOfGame:
15099       case MachinePlaysBlack:
15100         if (!WhiteOnMove(forwardMostMove)) {
15101             DisplayError(_("Wait until your turn"), 0);
15102             return;
15103         }
15104         break;
15105       default:
15106         DisplayError(_("No hint available"), 0);
15107         return;
15108     }
15109     SendToProgram("hint\n", &first);
15110     hintRequested = TRUE;
15111 }
15112
15113 void
15114 CreateBookEvent ()
15115 {
15116     ListGame * lg = (ListGame *) gameList.head;
15117     FILE *f;
15118     int nItem;
15119     static int secondTime = FALSE;
15120
15121     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15122         DisplayError(_("Game list not loaded or empty"), 0);
15123         return;
15124     }
15125
15126     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15127         fclose(f);
15128         secondTime++;
15129         DisplayNote(_("Book file exists! Try again for overwrite."));
15130         return;
15131     }
15132
15133     creatingBook = TRUE;
15134     secondTime = FALSE;
15135
15136     /* Get list size */
15137     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15138         LoadGame(f, nItem, "", TRUE);
15139         AddGameToBook(TRUE);
15140         lg = (ListGame *) lg->node.succ;
15141     }
15142
15143     creatingBook = FALSE;
15144     FlushBook();
15145 }
15146
15147 void
15148 BookEvent ()
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       case EditPosition:
15166         EditPositionDone(TRUE);
15167         break;
15168       case TwoMachinesPlay:
15169         return;
15170       default:
15171         break;
15172     }
15173     SendToProgram("bk\n", &first);
15174     bookOutput[0] = NULLCHAR;
15175     bookRequested = TRUE;
15176 }
15177
15178 void
15179 AboutGameEvent ()
15180 {
15181     char *tags = PGNTags(&gameInfo);
15182     TagsPopUp(tags, CmailMsg());
15183     free(tags);
15184 }
15185
15186 /* end button procedures */
15187
15188 void
15189 PrintPosition (FILE *fp, int move)
15190 {
15191     int i, j;
15192
15193     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15194         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15195             char c = PieceToChar(boards[move][i][j]);
15196             fputc(c == 'x' ? '.' : c, fp);
15197             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15198         }
15199     }
15200     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15201       fprintf(fp, "white to play\n");
15202     else
15203       fprintf(fp, "black to play\n");
15204 }
15205
15206 void
15207 PrintOpponents (FILE *fp)
15208 {
15209     if (gameInfo.white != NULL) {
15210         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15211     } else {
15212         fprintf(fp, "\n");
15213     }
15214 }
15215
15216 /* Find last component of program's own name, using some heuristics */
15217 void
15218 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15219 {
15220     char *p, *q, c;
15221     int local = (strcmp(host, "localhost") == 0);
15222     while (!local && (p = strchr(prog, ';')) != NULL) {
15223         p++;
15224         while (*p == ' ') p++;
15225         prog = p;
15226     }
15227     if (*prog == '"' || *prog == '\'') {
15228         q = strchr(prog + 1, *prog);
15229     } else {
15230         q = strchr(prog, ' ');
15231     }
15232     if (q == NULL) q = prog + strlen(prog);
15233     p = q;
15234     while (p >= prog && *p != '/' && *p != '\\') p--;
15235     p++;
15236     if(p == prog && *p == '"') p++;
15237     c = *q; *q = 0;
15238     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15239     memcpy(buf, p, q - p);
15240     buf[q - p] = NULLCHAR;
15241     if (!local) {
15242         strcat(buf, "@");
15243         strcat(buf, host);
15244     }
15245 }
15246
15247 char *
15248 TimeControlTagValue ()
15249 {
15250     char buf[MSG_SIZ];
15251     if (!appData.clockMode) {
15252       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15253     } else if (movesPerSession > 0) {
15254       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15255     } else if (timeIncrement == 0) {
15256       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15257     } else {
15258       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15259     }
15260     return StrSave(buf);
15261 }
15262
15263 void
15264 SetGameInfo ()
15265 {
15266     /* This routine is used only for certain modes */
15267     VariantClass v = gameInfo.variant;
15268     ChessMove r = GameUnfinished;
15269     char *p = NULL;
15270
15271     if(keepInfo) return;
15272
15273     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15274         r = gameInfo.result;
15275         p = gameInfo.resultDetails;
15276         gameInfo.resultDetails = NULL;
15277     }
15278     ClearGameInfo(&gameInfo);
15279     gameInfo.variant = v;
15280
15281     switch (gameMode) {
15282       case MachinePlaysWhite:
15283         gameInfo.event = StrSave( appData.pgnEventHeader );
15284         gameInfo.site = StrSave(HostName());
15285         gameInfo.date = PGNDate();
15286         gameInfo.round = StrSave("-");
15287         gameInfo.white = StrSave(first.tidy);
15288         gameInfo.black = StrSave(UserName());
15289         gameInfo.timeControl = TimeControlTagValue();
15290         break;
15291
15292       case MachinePlaysBlack:
15293         gameInfo.event = StrSave( appData.pgnEventHeader );
15294         gameInfo.site = StrSave(HostName());
15295         gameInfo.date = PGNDate();
15296         gameInfo.round = StrSave("-");
15297         gameInfo.white = StrSave(UserName());
15298         gameInfo.black = StrSave(first.tidy);
15299         gameInfo.timeControl = TimeControlTagValue();
15300         break;
15301
15302       case TwoMachinesPlay:
15303         gameInfo.event = StrSave( appData.pgnEventHeader );
15304         gameInfo.site = StrSave(HostName());
15305         gameInfo.date = PGNDate();
15306         if (roundNr > 0) {
15307             char buf[MSG_SIZ];
15308             snprintf(buf, MSG_SIZ, "%d", roundNr);
15309             gameInfo.round = StrSave(buf);
15310         } else {
15311             gameInfo.round = StrSave("-");
15312         }
15313         if (first.twoMachinesColor[0] == 'w') {
15314             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15315             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15316         } else {
15317             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15318             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15319         }
15320         gameInfo.timeControl = TimeControlTagValue();
15321         break;
15322
15323       case EditGame:
15324         gameInfo.event = StrSave("Edited game");
15325         gameInfo.site = StrSave(HostName());
15326         gameInfo.date = PGNDate();
15327         gameInfo.round = StrSave("-");
15328         gameInfo.white = StrSave("-");
15329         gameInfo.black = StrSave("-");
15330         gameInfo.result = r;
15331         gameInfo.resultDetails = p;
15332         break;
15333
15334       case EditPosition:
15335         gameInfo.event = StrSave("Edited position");
15336         gameInfo.site = StrSave(HostName());
15337         gameInfo.date = PGNDate();
15338         gameInfo.round = StrSave("-");
15339         gameInfo.white = StrSave("-");
15340         gameInfo.black = StrSave("-");
15341         break;
15342
15343       case IcsPlayingWhite:
15344       case IcsPlayingBlack:
15345       case IcsObserving:
15346       case IcsExamining:
15347         break;
15348
15349       case PlayFromGameFile:
15350         gameInfo.event = StrSave("Game from non-PGN file");
15351         gameInfo.site = StrSave(HostName());
15352         gameInfo.date = PGNDate();
15353         gameInfo.round = StrSave("-");
15354         gameInfo.white = StrSave("?");
15355         gameInfo.black = StrSave("?");
15356         break;
15357
15358       default:
15359         break;
15360     }
15361 }
15362
15363 void
15364 ReplaceComment (int index, char *text)
15365 {
15366     int len;
15367     char *p;
15368     float score;
15369
15370     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15371        pvInfoList[index-1].depth == len &&
15372        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15373        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15374     while (*text == '\n') text++;
15375     len = strlen(text);
15376     while (len > 0 && text[len - 1] == '\n') len--;
15377
15378     if (commentList[index] != NULL)
15379       free(commentList[index]);
15380
15381     if (len == 0) {
15382         commentList[index] = NULL;
15383         return;
15384     }
15385   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15386       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15387       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15388     commentList[index] = (char *) malloc(len + 2);
15389     strncpy(commentList[index], text, len);
15390     commentList[index][len] = '\n';
15391     commentList[index][len + 1] = NULLCHAR;
15392   } else {
15393     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15394     char *p;
15395     commentList[index] = (char *) malloc(len + 7);
15396     safeStrCpy(commentList[index], "{\n", 3);
15397     safeStrCpy(commentList[index]+2, text, len+1);
15398     commentList[index][len+2] = NULLCHAR;
15399     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15400     strcat(commentList[index], "\n}\n");
15401   }
15402 }
15403
15404 void
15405 CrushCRs (char *text)
15406 {
15407   char *p = text;
15408   char *q = text;
15409   char ch;
15410
15411   do {
15412     ch = *p++;
15413     if (ch == '\r') continue;
15414     *q++ = ch;
15415   } while (ch != '\0');
15416 }
15417
15418 void
15419 AppendComment (int index, char *text, Boolean addBraces)
15420 /* addBraces  tells if we should add {} */
15421 {
15422     int oldlen, len;
15423     char *old;
15424
15425 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15426     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15427
15428     CrushCRs(text);
15429     while (*text == '\n') text++;
15430     len = strlen(text);
15431     while (len > 0 && text[len - 1] == '\n') len--;
15432     text[len] = NULLCHAR;
15433
15434     if (len == 0) return;
15435
15436     if (commentList[index] != NULL) {
15437       Boolean addClosingBrace = addBraces;
15438         old = commentList[index];
15439         oldlen = strlen(old);
15440         while(commentList[index][oldlen-1] ==  '\n')
15441           commentList[index][--oldlen] = NULLCHAR;
15442         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15443         safeStrCpy(commentList[index], old, oldlen + len + 6);
15444         free(old);
15445         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15446         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15447           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15448           while (*text == '\n') { text++; len--; }
15449           commentList[index][--oldlen] = NULLCHAR;
15450       }
15451         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15452         else          strcat(commentList[index], "\n");
15453         strcat(commentList[index], text);
15454         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15455         else          strcat(commentList[index], "\n");
15456     } else {
15457         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15458         if(addBraces)
15459           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15460         else commentList[index][0] = NULLCHAR;
15461         strcat(commentList[index], text);
15462         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15463         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15464     }
15465 }
15466
15467 static char *
15468 FindStr (char * text, char * sub_text)
15469 {
15470     char * result = strstr( text, sub_text );
15471
15472     if( result != NULL ) {
15473         result += strlen( sub_text );
15474     }
15475
15476     return result;
15477 }
15478
15479 /* [AS] Try to extract PV info from PGN comment */
15480 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15481 char *
15482 GetInfoFromComment (int index, char * text)
15483 {
15484     char * sep = text, *p;
15485
15486     if( text != NULL && index > 0 ) {
15487         int score = 0;
15488         int depth = 0;
15489         int time = -1, sec = 0, deci;
15490         char * s_eval = FindStr( text, "[%eval " );
15491         char * s_emt = FindStr( text, "[%emt " );
15492
15493         if( s_eval != NULL || s_emt != NULL ) {
15494             /* New style */
15495             char delim;
15496
15497             if( s_eval != NULL ) {
15498                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15499                     return text;
15500                 }
15501
15502                 if( delim != ']' ) {
15503                     return text;
15504                 }
15505             }
15506
15507             if( s_emt != NULL ) {
15508             }
15509                 return text;
15510         }
15511         else {
15512             /* We expect something like: [+|-]nnn.nn/dd */
15513             int score_lo = 0;
15514
15515             if(*text != '{') return text; // [HGM] braces: must be normal comment
15516
15517             sep = strchr( text, '/' );
15518             if( sep == NULL || sep < (text+4) ) {
15519                 return text;
15520             }
15521
15522             p = text;
15523             if(p[1] == '(') { // comment starts with PV
15524                p = strchr(p, ')'); // locate end of PV
15525                if(p == NULL || sep < p+5) return text;
15526                // at this point we have something like "{(.*) +0.23/6 ..."
15527                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15528                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15529                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15530             }
15531             time = -1; sec = -1; deci = -1;
15532             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15533                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15534                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15535                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15536                 return text;
15537             }
15538
15539             if( score_lo < 0 || score_lo >= 100 ) {
15540                 return text;
15541             }
15542
15543             if(sec >= 0) time = 600*time + 10*sec; else
15544             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15545
15546             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15547
15548             /* [HGM] PV time: now locate end of PV info */
15549             while( *++sep >= '0' && *sep <= '9'); // strip depth
15550             if(time >= 0)
15551             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15552             if(sec >= 0)
15553             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15554             if(deci >= 0)
15555             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15556             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15557         }
15558
15559         if( depth <= 0 ) {
15560             return text;
15561         }
15562
15563         if( time < 0 ) {
15564             time = -1;
15565         }
15566
15567         pvInfoList[index-1].depth = depth;
15568         pvInfoList[index-1].score = score;
15569         pvInfoList[index-1].time  = 10*time; // centi-sec
15570         if(*sep == '}') *sep = 0; else *--sep = '{';
15571         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15572     }
15573     return sep;
15574 }
15575
15576 void
15577 SendToProgram (char *message, ChessProgramState *cps)
15578 {
15579     int count, outCount, error;
15580     char buf[MSG_SIZ];
15581
15582     if (cps->pr == NoProc) return;
15583     Attention(cps);
15584
15585     if (appData.debugMode) {
15586         TimeMark now;
15587         GetTimeMark(&now);
15588         fprintf(debugFP, "%ld >%-6s: %s",
15589                 SubtractTimeMarks(&now, &programStartTime),
15590                 cps->which, message);
15591         if(serverFP)
15592             fprintf(serverFP, "%ld >%-6s: %s",
15593                 SubtractTimeMarks(&now, &programStartTime),
15594                 cps->which, message), fflush(serverFP);
15595     }
15596
15597     count = strlen(message);
15598     outCount = OutputToProcess(cps->pr, message, count, &error);
15599     if (outCount < count && !exiting
15600                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15601       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15602       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15603         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15604             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15605                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15606                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15607                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15608             } else {
15609                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15610                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15611                 gameInfo.result = res;
15612             }
15613             gameInfo.resultDetails = StrSave(buf);
15614         }
15615         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15616         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15617     }
15618 }
15619
15620 void
15621 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15622 {
15623     char *end_str;
15624     char buf[MSG_SIZ];
15625     ChessProgramState *cps = (ChessProgramState *)closure;
15626
15627     if (isr != cps->isr) return; /* Killed intentionally */
15628     if (count <= 0) {
15629         if (count == 0) {
15630             RemoveInputSource(cps->isr);
15631             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15632                     _(cps->which), cps->program);
15633             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15634             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15635                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15636                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15637                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15638                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15639                 } else {
15640                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15641                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15642                     gameInfo.result = res;
15643                 }
15644                 gameInfo.resultDetails = StrSave(buf);
15645             }
15646             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15647             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15648         } else {
15649             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15650                     _(cps->which), cps->program);
15651             RemoveInputSource(cps->isr);
15652
15653             /* [AS] Program is misbehaving badly... kill it */
15654             if( count == -2 ) {
15655                 DestroyChildProcess( cps->pr, 9 );
15656                 cps->pr = NoProc;
15657             }
15658
15659             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15660         }
15661         return;
15662     }
15663
15664     if ((end_str = strchr(message, '\r')) != NULL)
15665       *end_str = NULLCHAR;
15666     if ((end_str = strchr(message, '\n')) != NULL)
15667       *end_str = NULLCHAR;
15668
15669     if (appData.debugMode) {
15670         TimeMark now; int print = 1;
15671         char *quote = ""; char c; int i;
15672
15673         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15674                 char start = message[0];
15675                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15676                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15677                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15678                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15679                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15680                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15681                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15682                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15683                    sscanf(message, "hint: %c", &c)!=1 && 
15684                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15685                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15686                     print = (appData.engineComments >= 2);
15687                 }
15688                 message[0] = start; // restore original message
15689         }
15690         if(print) {
15691                 GetTimeMark(&now);
15692                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15693                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15694                         quote,
15695                         message);
15696                 if(serverFP)
15697                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15698                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15699                         quote,
15700                         message), fflush(serverFP);
15701         }
15702     }
15703
15704     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15705     if (appData.icsEngineAnalyze) {
15706         if (strstr(message, "whisper") != NULL ||
15707              strstr(message, "kibitz") != NULL ||
15708             strstr(message, "tellics") != NULL) return;
15709     }
15710
15711     HandleMachineMove(message, cps);
15712 }
15713
15714
15715 void
15716 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15717 {
15718     char buf[MSG_SIZ];
15719     int seconds;
15720
15721     if( timeControl_2 > 0 ) {
15722         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15723             tc = timeControl_2;
15724         }
15725     }
15726     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15727     inc /= cps->timeOdds;
15728     st  /= cps->timeOdds;
15729
15730     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15731
15732     if (st > 0) {
15733       /* Set exact time per move, normally using st command */
15734       if (cps->stKludge) {
15735         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15736         seconds = st % 60;
15737         if (seconds == 0) {
15738           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15739         } else {
15740           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15741         }
15742       } else {
15743         snprintf(buf, MSG_SIZ, "st %d\n", st);
15744       }
15745     } else {
15746       /* Set conventional or incremental time control, using level command */
15747       if (seconds == 0) {
15748         /* Note old gnuchess bug -- minutes:seconds used to not work.
15749            Fixed in later versions, but still avoid :seconds
15750            when seconds is 0. */
15751         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15752       } else {
15753         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15754                  seconds, inc/1000.);
15755       }
15756     }
15757     SendToProgram(buf, cps);
15758
15759     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15760     /* Orthogonally, limit search to given depth */
15761     if (sd > 0) {
15762       if (cps->sdKludge) {
15763         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15764       } else {
15765         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15766       }
15767       SendToProgram(buf, cps);
15768     }
15769
15770     if(cps->nps >= 0) { /* [HGM] nps */
15771         if(cps->supportsNPS == FALSE)
15772           cps->nps = -1; // don't use if engine explicitly says not supported!
15773         else {
15774           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15775           SendToProgram(buf, cps);
15776         }
15777     }
15778 }
15779
15780 ChessProgramState *
15781 WhitePlayer ()
15782 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15783 {
15784     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15785        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15786         return &second;
15787     return &first;
15788 }
15789
15790 void
15791 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15792 {
15793     char message[MSG_SIZ];
15794     long time, otime;
15795
15796     /* Note: this routine must be called when the clocks are stopped
15797        or when they have *just* been set or switched; otherwise
15798        it will be off by the time since the current tick started.
15799     */
15800     if (machineWhite) {
15801         time = whiteTimeRemaining / 10;
15802         otime = blackTimeRemaining / 10;
15803     } else {
15804         time = blackTimeRemaining / 10;
15805         otime = whiteTimeRemaining / 10;
15806     }
15807     /* [HGM] translate opponent's time by time-odds factor */
15808     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15809
15810     if (time <= 0) time = 1;
15811     if (otime <= 0) otime = 1;
15812
15813     snprintf(message, MSG_SIZ, "time %ld\n", time);
15814     SendToProgram(message, cps);
15815
15816     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15817     SendToProgram(message, cps);
15818 }
15819
15820 int
15821 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15822 {
15823   char buf[MSG_SIZ];
15824   int len = strlen(name);
15825   int val;
15826
15827   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15828     (*p) += len + 1;
15829     sscanf(*p, "%d", &val);
15830     *loc = (val != 0);
15831     while (**p && **p != ' ')
15832       (*p)++;
15833     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15834     SendToProgram(buf, cps);
15835     return TRUE;
15836   }
15837   return FALSE;
15838 }
15839
15840 int
15841 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15842 {
15843   char buf[MSG_SIZ];
15844   int len = strlen(name);
15845   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15846     (*p) += len + 1;
15847     sscanf(*p, "%d", loc);
15848     while (**p && **p != ' ') (*p)++;
15849     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15850     SendToProgram(buf, cps);
15851     return TRUE;
15852   }
15853   return FALSE;
15854 }
15855
15856 int
15857 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15858 {
15859   char buf[MSG_SIZ];
15860   int len = strlen(name);
15861   if (strncmp((*p), name, len) == 0
15862       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15863     (*p) += len + 2;
15864     sscanf(*p, "%[^\"]", loc);
15865     while (**p && **p != '\"') (*p)++;
15866     if (**p == '\"') (*p)++;
15867     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15868     SendToProgram(buf, cps);
15869     return TRUE;
15870   }
15871   return FALSE;
15872 }
15873
15874 int
15875 ParseOption (Option *opt, ChessProgramState *cps)
15876 // [HGM] options: process the string that defines an engine option, and determine
15877 // name, type, default value, and allowed value range
15878 {
15879         char *p, *q, buf[MSG_SIZ];
15880         int n, min = (-1)<<31, max = 1<<31, def;
15881
15882         if(p = strstr(opt->name, " -spin ")) {
15883             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15884             if(max < min) max = min; // enforce consistency
15885             if(def < min) def = min;
15886             if(def > max) def = max;
15887             opt->value = def;
15888             opt->min = min;
15889             opt->max = max;
15890             opt->type = Spin;
15891         } else if((p = strstr(opt->name, " -slider "))) {
15892             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15893             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15894             if(max < min) max = min; // enforce consistency
15895             if(def < min) def = min;
15896             if(def > max) def = max;
15897             opt->value = def;
15898             opt->min = min;
15899             opt->max = max;
15900             opt->type = Spin; // Slider;
15901         } else if((p = strstr(opt->name, " -string "))) {
15902             opt->textValue = p+9;
15903             opt->type = TextBox;
15904         } else if((p = strstr(opt->name, " -file "))) {
15905             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15906             opt->textValue = p+7;
15907             opt->type = FileName; // FileName;
15908         } else if((p = strstr(opt->name, " -path "))) {
15909             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15910             opt->textValue = p+7;
15911             opt->type = PathName; // PathName;
15912         } else if(p = strstr(opt->name, " -check ")) {
15913             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15914             opt->value = (def != 0);
15915             opt->type = CheckBox;
15916         } else if(p = strstr(opt->name, " -combo ")) {
15917             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15918             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15919             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15920             opt->value = n = 0;
15921             while(q = StrStr(q, " /// ")) {
15922                 n++; *q = 0;    // count choices, and null-terminate each of them
15923                 q += 5;
15924                 if(*q == '*') { // remember default, which is marked with * prefix
15925                     q++;
15926                     opt->value = n;
15927                 }
15928                 cps->comboList[cps->comboCnt++] = q;
15929             }
15930             cps->comboList[cps->comboCnt++] = NULL;
15931             opt->max = n + 1;
15932             opt->type = ComboBox;
15933         } else if(p = strstr(opt->name, " -button")) {
15934             opt->type = Button;
15935         } else if(p = strstr(opt->name, " -save")) {
15936             opt->type = SaveButton;
15937         } else return FALSE;
15938         *p = 0; // terminate option name
15939         // now look if the command-line options define a setting for this engine option.
15940         if(cps->optionSettings && cps->optionSettings[0])
15941             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15942         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15943           snprintf(buf, MSG_SIZ, "option %s", p);
15944                 if(p = strstr(buf, ",")) *p = 0;
15945                 if(q = strchr(buf, '=')) switch(opt->type) {
15946                     case ComboBox:
15947                         for(n=0; n<opt->max; n++)
15948                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15949                         break;
15950                     case TextBox:
15951                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15952                         break;
15953                     case Spin:
15954                     case CheckBox:
15955                         opt->value = atoi(q+1);
15956                     default:
15957                         break;
15958                 }
15959                 strcat(buf, "\n");
15960                 SendToProgram(buf, cps);
15961         }
15962         return TRUE;
15963 }
15964
15965 void
15966 FeatureDone (ChessProgramState *cps, int val)
15967 {
15968   DelayedEventCallback cb = GetDelayedEvent();
15969   if ((cb == InitBackEnd3 && cps == &first) ||
15970       (cb == SettingsMenuIfReady && cps == &second) ||
15971       (cb == LoadEngine) ||
15972       (cb == TwoMachinesEventIfReady)) {
15973     CancelDelayedEvent();
15974     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15975   }
15976   cps->initDone = val;
15977 }
15978
15979 /* Parse feature command from engine */
15980 void
15981 ParseFeatures (char *args, ChessProgramState *cps)
15982 {
15983   char *p = args;
15984   char *q;
15985   int val;
15986   char buf[MSG_SIZ];
15987
15988   for (;;) {
15989     while (*p == ' ') p++;
15990     if (*p == NULLCHAR) return;
15991
15992     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15993     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15994     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15995     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15996     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15997     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15998     if (BoolFeature(&p, "reuse", &val, cps)) {
15999       /* Engine can disable reuse, but can't enable it if user said no */
16000       if (!val) cps->reuse = FALSE;
16001       continue;
16002     }
16003     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16004     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16005       if (gameMode == TwoMachinesPlay) {
16006         DisplayTwoMachinesTitle();
16007       } else {
16008         DisplayTitle("");
16009       }
16010       continue;
16011     }
16012     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16013     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16014     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16015     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16016     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16017     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16018     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16019     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16020     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16021     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16022     if (IntFeature(&p, "done", &val, cps)) {
16023       FeatureDone(cps, val);
16024       continue;
16025     }
16026     /* Added by Tord: */
16027     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16028     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16029     /* End of additions by Tord */
16030
16031     /* [HGM] added features: */
16032     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16033     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16034     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16035     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16036     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16037     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16038     if (StringFeature(&p, "option", buf, cps)) {
16039         FREE(cps->option[cps->nrOptions].name);
16040         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16041         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16042         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16043           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16044             SendToProgram(buf, cps);
16045             continue;
16046         }
16047         if(cps->nrOptions >= MAX_OPTIONS) {
16048             cps->nrOptions--;
16049             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16050             DisplayError(buf, 0);
16051         }
16052         continue;
16053     }
16054     /* End of additions by HGM */
16055
16056     /* unknown feature: complain and skip */
16057     q = p;
16058     while (*q && *q != '=') q++;
16059     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16060     SendToProgram(buf, cps);
16061     p = q;
16062     if (*p == '=') {
16063       p++;
16064       if (*p == '\"') {
16065         p++;
16066         while (*p && *p != '\"') p++;
16067         if (*p == '\"') p++;
16068       } else {
16069         while (*p && *p != ' ') p++;
16070       }
16071     }
16072   }
16073
16074 }
16075
16076 void
16077 PeriodicUpdatesEvent (int newState)
16078 {
16079     if (newState == appData.periodicUpdates)
16080       return;
16081
16082     appData.periodicUpdates=newState;
16083
16084     /* Display type changes, so update it now */
16085 //    DisplayAnalysis();
16086
16087     /* Get the ball rolling again... */
16088     if (newState) {
16089         AnalysisPeriodicEvent(1);
16090         StartAnalysisClock();
16091     }
16092 }
16093
16094 void
16095 PonderNextMoveEvent (int newState)
16096 {
16097     if (newState == appData.ponderNextMove) return;
16098     if (gameMode == EditPosition) EditPositionDone(TRUE);
16099     if (newState) {
16100         SendToProgram("hard\n", &first);
16101         if (gameMode == TwoMachinesPlay) {
16102             SendToProgram("hard\n", &second);
16103         }
16104     } else {
16105         SendToProgram("easy\n", &first);
16106         thinkOutput[0] = NULLCHAR;
16107         if (gameMode == TwoMachinesPlay) {
16108             SendToProgram("easy\n", &second);
16109         }
16110     }
16111     appData.ponderNextMove = newState;
16112 }
16113
16114 void
16115 NewSettingEvent (int option, int *feature, char *command, int value)
16116 {
16117     char buf[MSG_SIZ];
16118
16119     if (gameMode == EditPosition) EditPositionDone(TRUE);
16120     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16121     if(feature == NULL || *feature) SendToProgram(buf, &first);
16122     if (gameMode == TwoMachinesPlay) {
16123         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16124     }
16125 }
16126
16127 void
16128 ShowThinkingEvent ()
16129 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16130 {
16131     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16132     int newState = appData.showThinking
16133         // [HGM] thinking: other features now need thinking output as well
16134         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16135
16136     if (oldState == newState) return;
16137     oldState = newState;
16138     if (gameMode == EditPosition) EditPositionDone(TRUE);
16139     if (oldState) {
16140         SendToProgram("post\n", &first);
16141         if (gameMode == TwoMachinesPlay) {
16142             SendToProgram("post\n", &second);
16143         }
16144     } else {
16145         SendToProgram("nopost\n", &first);
16146         thinkOutput[0] = NULLCHAR;
16147         if (gameMode == TwoMachinesPlay) {
16148             SendToProgram("nopost\n", &second);
16149         }
16150     }
16151 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16152 }
16153
16154 void
16155 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16156 {
16157   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16158   if (pr == NoProc) return;
16159   AskQuestion(title, question, replyPrefix, pr);
16160 }
16161
16162 void
16163 TypeInEvent (char firstChar)
16164 {
16165     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
16166         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16167         gameMode == AnalyzeMode || gameMode == EditGame || 
16168         gameMode == EditPosition || gameMode == IcsExamining ||
16169         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16170         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16171                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16172                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16173         gameMode == Training) PopUpMoveDialog(firstChar);
16174 }
16175
16176 void
16177 TypeInDoneEvent (char *move)
16178 {
16179         Board board;
16180         int n, fromX, fromY, toX, toY;
16181         char promoChar;
16182         ChessMove moveType;
16183
16184         // [HGM] FENedit
16185         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16186                 EditPositionPasteFEN(move);
16187                 return;
16188         }
16189         // [HGM] movenum: allow move number to be typed in any mode
16190         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16191           ToNrEvent(2*n-1);
16192           return;
16193         }
16194         // undocumented kludge: allow command-line option to be typed in!
16195         // (potentially fatal, and does not implement the effect of the option.)
16196         // should only be used for options that are values on which future decisions will be made,
16197         // and definitely not on options that would be used during initialization.
16198         if(strstr(move, "!!! -") == move) {
16199             ParseArgsFromString(move+4);
16200             return;
16201         }
16202
16203       if (gameMode != EditGame && currentMove != forwardMostMove && 
16204         gameMode != Training) {
16205         DisplayMoveError(_("Displayed move is not current"));
16206       } else {
16207         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16208           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16209         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16210         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16211           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16212           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16213         } else {
16214           DisplayMoveError(_("Could not parse move"));
16215         }
16216       }
16217 }
16218
16219 void
16220 DisplayMove (int moveNumber)
16221 {
16222     char message[MSG_SIZ];
16223     char res[MSG_SIZ];
16224     char cpThinkOutput[MSG_SIZ];
16225
16226     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16227
16228     if (moveNumber == forwardMostMove - 1 ||
16229         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16230
16231         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16232
16233         if (strchr(cpThinkOutput, '\n')) {
16234             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16235         }
16236     } else {
16237         *cpThinkOutput = NULLCHAR;
16238     }
16239
16240     /* [AS] Hide thinking from human user */
16241     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16242         *cpThinkOutput = NULLCHAR;
16243         if( thinkOutput[0] != NULLCHAR ) {
16244             int i;
16245
16246             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16247                 cpThinkOutput[i] = '.';
16248             }
16249             cpThinkOutput[i] = NULLCHAR;
16250             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16251         }
16252     }
16253
16254     if (moveNumber == forwardMostMove - 1 &&
16255         gameInfo.resultDetails != NULL) {
16256         if (gameInfo.resultDetails[0] == NULLCHAR) {
16257           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16258         } else {
16259           snprintf(res, MSG_SIZ, " {%s} %s",
16260                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16261         }
16262     } else {
16263         res[0] = NULLCHAR;
16264     }
16265
16266     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16267         DisplayMessage(res, cpThinkOutput);
16268     } else {
16269       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16270                 WhiteOnMove(moveNumber) ? " " : ".. ",
16271                 parseList[moveNumber], res);
16272         DisplayMessage(message, cpThinkOutput);
16273     }
16274 }
16275
16276 void
16277 DisplayComment (int moveNumber, char *text)
16278 {
16279     char title[MSG_SIZ];
16280
16281     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16282       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16283     } else {
16284       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16285               WhiteOnMove(moveNumber) ? " " : ".. ",
16286               parseList[moveNumber]);
16287     }
16288     if (text != NULL && (appData.autoDisplayComment || commentUp))
16289         CommentPopUp(title, text);
16290 }
16291
16292 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16293  * might be busy thinking or pondering.  It can be omitted if your
16294  * gnuchess is configured to stop thinking immediately on any user
16295  * input.  However, that gnuchess feature depends on the FIONREAD
16296  * ioctl, which does not work properly on some flavors of Unix.
16297  */
16298 void
16299 Attention (ChessProgramState *cps)
16300 {
16301 #if ATTENTION
16302     if (!cps->useSigint) return;
16303     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16304     switch (gameMode) {
16305       case MachinePlaysWhite:
16306       case MachinePlaysBlack:
16307       case TwoMachinesPlay:
16308       case IcsPlayingWhite:
16309       case IcsPlayingBlack:
16310       case AnalyzeMode:
16311       case AnalyzeFile:
16312         /* Skip if we know it isn't thinking */
16313         if (!cps->maybeThinking) return;
16314         if (appData.debugMode)
16315           fprintf(debugFP, "Interrupting %s\n", cps->which);
16316         InterruptChildProcess(cps->pr);
16317         cps->maybeThinking = FALSE;
16318         break;
16319       default:
16320         break;
16321     }
16322 #endif /*ATTENTION*/
16323 }
16324
16325 int
16326 CheckFlags ()
16327 {
16328     if (whiteTimeRemaining <= 0) {
16329         if (!whiteFlag) {
16330             whiteFlag = TRUE;
16331             if (appData.icsActive) {
16332                 if (appData.autoCallFlag &&
16333                     gameMode == IcsPlayingBlack && !blackFlag) {
16334                   SendToICS(ics_prefix);
16335                   SendToICS("flag\n");
16336                 }
16337             } else {
16338                 if (blackFlag) {
16339                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16340                 } else {
16341                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16342                     if (appData.autoCallFlag) {
16343                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16344                         return TRUE;
16345                     }
16346                 }
16347             }
16348         }
16349     }
16350     if (blackTimeRemaining <= 0) {
16351         if (!blackFlag) {
16352             blackFlag = TRUE;
16353             if (appData.icsActive) {
16354                 if (appData.autoCallFlag &&
16355                     gameMode == IcsPlayingWhite && !whiteFlag) {
16356                   SendToICS(ics_prefix);
16357                   SendToICS("flag\n");
16358                 }
16359             } else {
16360                 if (whiteFlag) {
16361                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16362                 } else {
16363                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16364                     if (appData.autoCallFlag) {
16365                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16366                         return TRUE;
16367                     }
16368                 }
16369             }
16370         }
16371     }
16372     return FALSE;
16373 }
16374
16375 void
16376 CheckTimeControl ()
16377 {
16378     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16379         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16380
16381     /*
16382      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16383      */
16384     if ( !WhiteOnMove(forwardMostMove) ) {
16385         /* White made time control */
16386         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16387         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16388         /* [HGM] time odds: correct new time quota for time odds! */
16389                                             / WhitePlayer()->timeOdds;
16390         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16391     } else {
16392         lastBlack -= blackTimeRemaining;
16393         /* Black made time control */
16394         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16395                                             / WhitePlayer()->other->timeOdds;
16396         lastWhite = whiteTimeRemaining;
16397     }
16398 }
16399
16400 void
16401 DisplayBothClocks ()
16402 {
16403     int wom = gameMode == EditPosition ?
16404       !blackPlaysFirst : WhiteOnMove(currentMove);
16405     DisplayWhiteClock(whiteTimeRemaining, wom);
16406     DisplayBlackClock(blackTimeRemaining, !wom);
16407 }
16408
16409
16410 /* Timekeeping seems to be a portability nightmare.  I think everyone
16411    has ftime(), but I'm really not sure, so I'm including some ifdefs
16412    to use other calls if you don't.  Clocks will be less accurate if
16413    you have neither ftime nor gettimeofday.
16414 */
16415
16416 /* VS 2008 requires the #include outside of the function */
16417 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16418 #include <sys/timeb.h>
16419 #endif
16420
16421 /* Get the current time as a TimeMark */
16422 void
16423 GetTimeMark (TimeMark *tm)
16424 {
16425 #if HAVE_GETTIMEOFDAY
16426
16427     struct timeval timeVal;
16428     struct timezone timeZone;
16429
16430     gettimeofday(&timeVal, &timeZone);
16431     tm->sec = (long) timeVal.tv_sec;
16432     tm->ms = (int) (timeVal.tv_usec / 1000L);
16433
16434 #else /*!HAVE_GETTIMEOFDAY*/
16435 #if HAVE_FTIME
16436
16437 // include <sys/timeb.h> / moved to just above start of function
16438     struct timeb timeB;
16439
16440     ftime(&timeB);
16441     tm->sec = (long) timeB.time;
16442     tm->ms = (int) timeB.millitm;
16443
16444 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16445     tm->sec = (long) time(NULL);
16446     tm->ms = 0;
16447 #endif
16448 #endif
16449 }
16450
16451 /* Return the difference in milliseconds between two
16452    time marks.  We assume the difference will fit in a long!
16453 */
16454 long
16455 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16456 {
16457     return 1000L*(tm2->sec - tm1->sec) +
16458            (long) (tm2->ms - tm1->ms);
16459 }
16460
16461
16462 /*
16463  * Code to manage the game clocks.
16464  *
16465  * In tournament play, black starts the clock and then white makes a move.
16466  * We give the human user a slight advantage if he is playing white---the
16467  * clocks don't run until he makes his first move, so it takes zero time.
16468  * Also, we don't account for network lag, so we could get out of sync
16469  * with GNU Chess's clock -- but then, referees are always right.
16470  */
16471
16472 static TimeMark tickStartTM;
16473 static long intendedTickLength;
16474
16475 long
16476 NextTickLength (long timeRemaining)
16477 {
16478     long nominalTickLength, nextTickLength;
16479
16480     if (timeRemaining > 0L && timeRemaining <= 10000L)
16481       nominalTickLength = 100L;
16482     else
16483       nominalTickLength = 1000L;
16484     nextTickLength = timeRemaining % nominalTickLength;
16485     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16486
16487     return nextTickLength;
16488 }
16489
16490 /* Adjust clock one minute up or down */
16491 void
16492 AdjustClock (Boolean which, int dir)
16493 {
16494     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16495     if(which) blackTimeRemaining += 60000*dir;
16496     else      whiteTimeRemaining += 60000*dir;
16497     DisplayBothClocks();
16498     adjustedClock = TRUE;
16499 }
16500
16501 /* Stop clocks and reset to a fresh time control */
16502 void
16503 ResetClocks ()
16504 {
16505     (void) StopClockTimer();
16506     if (appData.icsActive) {
16507         whiteTimeRemaining = blackTimeRemaining = 0;
16508     } else if (searchTime) {
16509         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16510         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16511     } else { /* [HGM] correct new time quote for time odds */
16512         whiteTC = blackTC = fullTimeControlString;
16513         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16514         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16515     }
16516     if (whiteFlag || blackFlag) {
16517         DisplayTitle("");
16518         whiteFlag = blackFlag = FALSE;
16519     }
16520     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16521     DisplayBothClocks();
16522     adjustedClock = FALSE;
16523 }
16524
16525 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16526
16527 /* Decrement running clock by amount of time that has passed */
16528 void
16529 DecrementClocks ()
16530 {
16531     long timeRemaining;
16532     long lastTickLength, fudge;
16533     TimeMark now;
16534
16535     if (!appData.clockMode) return;
16536     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16537
16538     GetTimeMark(&now);
16539
16540     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16541
16542     /* Fudge if we woke up a little too soon */
16543     fudge = intendedTickLength - lastTickLength;
16544     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16545
16546     if (WhiteOnMove(forwardMostMove)) {
16547         if(whiteNPS >= 0) lastTickLength = 0;
16548         timeRemaining = whiteTimeRemaining -= lastTickLength;
16549         if(timeRemaining < 0 && !appData.icsActive) {
16550             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16551             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16552                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16553                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16554             }
16555         }
16556         DisplayWhiteClock(whiteTimeRemaining - fudge,
16557                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16558     } else {
16559         if(blackNPS >= 0) lastTickLength = 0;
16560         timeRemaining = blackTimeRemaining -= lastTickLength;
16561         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16562             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16563             if(suddenDeath) {
16564                 blackStartMove = forwardMostMove;
16565                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16566             }
16567         }
16568         DisplayBlackClock(blackTimeRemaining - fudge,
16569                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16570     }
16571     if (CheckFlags()) return;
16572
16573     if(twoBoards) { // count down secondary board's clocks as well
16574         activePartnerTime -= lastTickLength;
16575         partnerUp = 1;
16576         if(activePartner == 'W')
16577             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16578         else
16579             DisplayBlackClock(activePartnerTime, TRUE);
16580         partnerUp = 0;
16581     }
16582
16583     tickStartTM = now;
16584     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16585     StartClockTimer(intendedTickLength);
16586
16587     /* if the time remaining has fallen below the alarm threshold, sound the
16588      * alarm. if the alarm has sounded and (due to a takeback or time control
16589      * with increment) the time remaining has increased to a level above the
16590      * threshold, reset the alarm so it can sound again.
16591      */
16592
16593     if (appData.icsActive && appData.icsAlarm) {
16594
16595         /* make sure we are dealing with the user's clock */
16596         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16597                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16598            )) return;
16599
16600         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16601             alarmSounded = FALSE;
16602         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16603             PlayAlarmSound();
16604             alarmSounded = TRUE;
16605         }
16606     }
16607 }
16608
16609
16610 /* A player has just moved, so stop the previously running
16611    clock and (if in clock mode) start the other one.
16612    We redisplay both clocks in case we're in ICS mode, because
16613    ICS gives us an update to both clocks after every move.
16614    Note that this routine is called *after* forwardMostMove
16615    is updated, so the last fractional tick must be subtracted
16616    from the color that is *not* on move now.
16617 */
16618 void
16619 SwitchClocks (int newMoveNr)
16620 {
16621     long lastTickLength;
16622     TimeMark now;
16623     int flagged = FALSE;
16624
16625     GetTimeMark(&now);
16626
16627     if (StopClockTimer() && appData.clockMode) {
16628         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16629         if (!WhiteOnMove(forwardMostMove)) {
16630             if(blackNPS >= 0) lastTickLength = 0;
16631             blackTimeRemaining -= lastTickLength;
16632            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16633 //         if(pvInfoList[forwardMostMove].time == -1)
16634                  pvInfoList[forwardMostMove].time =               // use GUI time
16635                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16636         } else {
16637            if(whiteNPS >= 0) lastTickLength = 0;
16638            whiteTimeRemaining -= lastTickLength;
16639            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16640 //         if(pvInfoList[forwardMostMove].time == -1)
16641                  pvInfoList[forwardMostMove].time =
16642                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16643         }
16644         flagged = CheckFlags();
16645     }
16646     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16647     CheckTimeControl();
16648
16649     if (flagged || !appData.clockMode) return;
16650
16651     switch (gameMode) {
16652       case MachinePlaysBlack:
16653       case MachinePlaysWhite:
16654       case BeginningOfGame:
16655         if (pausing) return;
16656         break;
16657
16658       case EditGame:
16659       case PlayFromGameFile:
16660       case IcsExamining:
16661         return;
16662
16663       default:
16664         break;
16665     }
16666
16667     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16668         if(WhiteOnMove(forwardMostMove))
16669              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16670         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16671     }
16672
16673     tickStartTM = now;
16674     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16675       whiteTimeRemaining : blackTimeRemaining);
16676     StartClockTimer(intendedTickLength);
16677 }
16678
16679
16680 /* Stop both clocks */
16681 void
16682 StopClocks ()
16683 {
16684     long lastTickLength;
16685     TimeMark now;
16686
16687     if (!StopClockTimer()) return;
16688     if (!appData.clockMode) return;
16689
16690     GetTimeMark(&now);
16691
16692     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16693     if (WhiteOnMove(forwardMostMove)) {
16694         if(whiteNPS >= 0) lastTickLength = 0;
16695         whiteTimeRemaining -= lastTickLength;
16696         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16697     } else {
16698         if(blackNPS >= 0) lastTickLength = 0;
16699         blackTimeRemaining -= lastTickLength;
16700         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16701     }
16702     CheckFlags();
16703 }
16704
16705 /* Start clock of player on move.  Time may have been reset, so
16706    if clock is already running, stop and restart it. */
16707 void
16708 StartClocks ()
16709 {
16710     (void) StopClockTimer(); /* in case it was running already */
16711     DisplayBothClocks();
16712     if (CheckFlags()) return;
16713
16714     if (!appData.clockMode) return;
16715     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16716
16717     GetTimeMark(&tickStartTM);
16718     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16719       whiteTimeRemaining : blackTimeRemaining);
16720
16721    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16722     whiteNPS = blackNPS = -1;
16723     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16724        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16725         whiteNPS = first.nps;
16726     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16727        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16728         blackNPS = first.nps;
16729     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16730         whiteNPS = second.nps;
16731     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16732         blackNPS = second.nps;
16733     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16734
16735     StartClockTimer(intendedTickLength);
16736 }
16737
16738 char *
16739 TimeString (long ms)
16740 {
16741     long second, minute, hour, day;
16742     char *sign = "";
16743     static char buf[32];
16744
16745     if (ms > 0 && ms <= 9900) {
16746       /* convert milliseconds to tenths, rounding up */
16747       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16748
16749       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16750       return buf;
16751     }
16752
16753     /* convert milliseconds to seconds, rounding up */
16754     /* use floating point to avoid strangeness of integer division
16755        with negative dividends on many machines */
16756     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16757
16758     if (second < 0) {
16759         sign = "-";
16760         second = -second;
16761     }
16762
16763     day = second / (60 * 60 * 24);
16764     second = second % (60 * 60 * 24);
16765     hour = second / (60 * 60);
16766     second = second % (60 * 60);
16767     minute = second / 60;
16768     second = second % 60;
16769
16770     if (day > 0)
16771       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16772               sign, day, hour, minute, second);
16773     else if (hour > 0)
16774       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16775     else
16776       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16777
16778     return buf;
16779 }
16780
16781
16782 /*
16783  * This is necessary because some C libraries aren't ANSI C compliant yet.
16784  */
16785 char *
16786 StrStr (char *string, char *match)
16787 {
16788     int i, length;
16789
16790     length = strlen(match);
16791
16792     for (i = strlen(string) - length; i >= 0; i--, string++)
16793       if (!strncmp(match, string, length))
16794         return string;
16795
16796     return NULL;
16797 }
16798
16799 char *
16800 StrCaseStr (char *string, char *match)
16801 {
16802     int i, j, length;
16803
16804     length = strlen(match);
16805
16806     for (i = strlen(string) - length; i >= 0; i--, string++) {
16807         for (j = 0; j < length; j++) {
16808             if (ToLower(match[j]) != ToLower(string[j]))
16809               break;
16810         }
16811         if (j == length) return string;
16812     }
16813
16814     return NULL;
16815 }
16816
16817 #ifndef _amigados
16818 int
16819 StrCaseCmp (char *s1, char *s2)
16820 {
16821     char c1, c2;
16822
16823     for (;;) {
16824         c1 = ToLower(*s1++);
16825         c2 = ToLower(*s2++);
16826         if (c1 > c2) return 1;
16827         if (c1 < c2) return -1;
16828         if (c1 == NULLCHAR) return 0;
16829     }
16830 }
16831
16832
16833 int
16834 ToLower (int c)
16835 {
16836     return isupper(c) ? tolower(c) : c;
16837 }
16838
16839
16840 int
16841 ToUpper (int c)
16842 {
16843     return islower(c) ? toupper(c) : c;
16844 }
16845 #endif /* !_amigados    */
16846
16847 char *
16848 StrSave (char *s)
16849 {
16850   char *ret;
16851
16852   if ((ret = (char *) malloc(strlen(s) + 1)))
16853     {
16854       safeStrCpy(ret, s, strlen(s)+1);
16855     }
16856   return ret;
16857 }
16858
16859 char *
16860 StrSavePtr (char *s, char **savePtr)
16861 {
16862     if (*savePtr) {
16863         free(*savePtr);
16864     }
16865     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16866       safeStrCpy(*savePtr, s, strlen(s)+1);
16867     }
16868     return(*savePtr);
16869 }
16870
16871 char *
16872 PGNDate ()
16873 {
16874     time_t clock;
16875     struct tm *tm;
16876     char buf[MSG_SIZ];
16877
16878     clock = time((time_t *)NULL);
16879     tm = localtime(&clock);
16880     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16881             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16882     return StrSave(buf);
16883 }
16884
16885
16886 char *
16887 PositionToFEN (int move, char *overrideCastling)
16888 {
16889     int i, j, fromX, fromY, toX, toY;
16890     int whiteToPlay;
16891     char buf[MSG_SIZ];
16892     char *p, *q;
16893     int emptycount;
16894     ChessSquare piece;
16895
16896     whiteToPlay = (gameMode == EditPosition) ?
16897       !blackPlaysFirst : (move % 2 == 0);
16898     p = buf;
16899
16900     /* Piece placement data */
16901     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16902         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16903         emptycount = 0;
16904         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16905             if (boards[move][i][j] == EmptySquare) {
16906                 emptycount++;
16907             } else { ChessSquare piece = boards[move][i][j];
16908                 if (emptycount > 0) {
16909                     if(emptycount<10) /* [HGM] can be >= 10 */
16910                         *p++ = '0' + emptycount;
16911                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16912                     emptycount = 0;
16913                 }
16914                 if(PieceToChar(piece) == '+') {
16915                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16916                     *p++ = '+';
16917                     piece = (ChessSquare)(DEMOTED piece);
16918                 }
16919                 *p++ = PieceToChar(piece);
16920                 if(p[-1] == '~') {
16921                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16922                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16923                     *p++ = '~';
16924                 }
16925             }
16926         }
16927         if (emptycount > 0) {
16928             if(emptycount<10) /* [HGM] can be >= 10 */
16929                 *p++ = '0' + emptycount;
16930             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16931             emptycount = 0;
16932         }
16933         *p++ = '/';
16934     }
16935     *(p - 1) = ' ';
16936
16937     /* [HGM] print Crazyhouse or Shogi holdings */
16938     if( gameInfo.holdingsWidth ) {
16939         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16940         q = p;
16941         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16942             piece = boards[move][i][BOARD_WIDTH-1];
16943             if( piece != EmptySquare )
16944               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16945                   *p++ = PieceToChar(piece);
16946         }
16947         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16948             piece = boards[move][BOARD_HEIGHT-i-1][0];
16949             if( piece != EmptySquare )
16950               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16951                   *p++ = PieceToChar(piece);
16952         }
16953
16954         if( q == p ) *p++ = '-';
16955         *p++ = ']';
16956         *p++ = ' ';
16957     }
16958
16959     /* Active color */
16960     *p++ = whiteToPlay ? 'w' : 'b';
16961     *p++ = ' ';
16962
16963   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16964     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16965   } else {
16966   if(nrCastlingRights) {
16967      q = p;
16968      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16969        /* [HGM] write directly from rights */
16970            if(boards[move][CASTLING][2] != NoRights &&
16971               boards[move][CASTLING][0] != NoRights   )
16972                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16973            if(boards[move][CASTLING][2] != NoRights &&
16974               boards[move][CASTLING][1] != NoRights   )
16975                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16976            if(boards[move][CASTLING][5] != NoRights &&
16977               boards[move][CASTLING][3] != NoRights   )
16978                 *p++ = boards[move][CASTLING][3] + AAA;
16979            if(boards[move][CASTLING][5] != NoRights &&
16980               boards[move][CASTLING][4] != NoRights   )
16981                 *p++ = boards[move][CASTLING][4] + AAA;
16982      } else {
16983
16984         /* [HGM] write true castling rights */
16985         if( nrCastlingRights == 6 ) {
16986             int q, k=0;
16987             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16988                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16989             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16990                  boards[move][CASTLING][2] != NoRights  );
16991             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16992                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16993                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16994                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16995                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16996             }
16997             if(q) *p++ = 'Q';
16998             k = 0;
16999             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17000                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17001             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17002                  boards[move][CASTLING][5] != NoRights  );
17003             if(gameInfo.variant == VariantSChess) {
17004                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17005                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17006                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17007                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17008             }
17009             if(q) *p++ = 'q';
17010         }
17011      }
17012      if (q == p) *p++ = '-'; /* No castling rights */
17013      *p++ = ' ';
17014   }
17015
17016   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17017      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17018     /* En passant target square */
17019     if (move > backwardMostMove) {
17020         fromX = moveList[move - 1][0] - AAA;
17021         fromY = moveList[move - 1][1] - ONE;
17022         toX = moveList[move - 1][2] - AAA;
17023         toY = moveList[move - 1][3] - ONE;
17024         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17025             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17026             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17027             fromX == toX) {
17028             /* 2-square pawn move just happened */
17029             *p++ = toX + AAA;
17030             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17031         } else {
17032             *p++ = '-';
17033         }
17034     } else if(move == backwardMostMove) {
17035         // [HGM] perhaps we should always do it like this, and forget the above?
17036         if((signed char)boards[move][EP_STATUS] >= 0) {
17037             *p++ = boards[move][EP_STATUS] + AAA;
17038             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17039         } else {
17040             *p++ = '-';
17041         }
17042     } else {
17043         *p++ = '-';
17044     }
17045     *p++ = ' ';
17046   }
17047   }
17048
17049     /* [HGM] find reversible plies */
17050     {   int i = 0, j=move;
17051
17052         if (appData.debugMode) { int k;
17053             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17054             for(k=backwardMostMove; k<=forwardMostMove; k++)
17055                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17056
17057         }
17058
17059         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17060         if( j == backwardMostMove ) i += initialRulePlies;
17061         sprintf(p, "%d ", i);
17062         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17063     }
17064     /* Fullmove number */
17065     sprintf(p, "%d", (move / 2) + 1);
17066
17067     return StrSave(buf);
17068 }
17069
17070 Boolean
17071 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17072 {
17073     int i, j;
17074     char *p, c;
17075     int emptycount, virgin[BOARD_FILES];
17076     ChessSquare piece;
17077
17078     p = fen;
17079
17080     /* [HGM] by default clear Crazyhouse holdings, if present */
17081     if(gameInfo.holdingsWidth) {
17082        for(i=0; i<BOARD_HEIGHT; i++) {
17083            board[i][0]             = EmptySquare; /* black holdings */
17084            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17085            board[i][1]             = (ChessSquare) 0; /* black counts */
17086            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17087        }
17088     }
17089
17090     /* Piece placement data */
17091     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17092         j = 0;
17093         for (;;) {
17094             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17095                 if (*p == '/') p++;
17096                 emptycount = gameInfo.boardWidth - j;
17097                 while (emptycount--)
17098                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17099                 break;
17100 #if(BOARD_FILES >= 10)
17101             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17102                 p++; emptycount=10;
17103                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17104                 while (emptycount--)
17105                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17106 #endif
17107             } else if (isdigit(*p)) {
17108                 emptycount = *p++ - '0';
17109                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17110                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17111                 while (emptycount--)
17112                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17113             } else if (*p == '+' || isalpha(*p)) {
17114                 if (j >= gameInfo.boardWidth) return FALSE;
17115                 if(*p=='+') {
17116                     piece = CharToPiece(*++p);
17117                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17118                     piece = (ChessSquare) (PROMOTED piece ); p++;
17119                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17120                 } else piece = CharToPiece(*p++);
17121
17122                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17123                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17124                     piece = (ChessSquare) (PROMOTED piece);
17125                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17126                     p++;
17127                 }
17128                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17129             } else {
17130                 return FALSE;
17131             }
17132         }
17133     }
17134     while (*p == '/' || *p == ' ') p++;
17135
17136     /* [HGM] look for Crazyhouse holdings here */
17137     while(*p==' ') p++;
17138     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17139         if(*p == '[') p++;
17140         if(*p == '-' ) p++; /* empty holdings */ else {
17141             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17142             /* if we would allow FEN reading to set board size, we would   */
17143             /* have to add holdings and shift the board read so far here   */
17144             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17145                 p++;
17146                 if((int) piece >= (int) BlackPawn ) {
17147                     i = (int)piece - (int)BlackPawn;
17148                     i = PieceToNumber((ChessSquare)i);
17149                     if( i >= gameInfo.holdingsSize ) return FALSE;
17150                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17151                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17152                 } else {
17153                     i = (int)piece - (int)WhitePawn;
17154                     i = PieceToNumber((ChessSquare)i);
17155                     if( i >= gameInfo.holdingsSize ) return FALSE;
17156                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17157                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17158                 }
17159             }
17160         }
17161         if(*p == ']') p++;
17162     }
17163
17164     while(*p == ' ') p++;
17165
17166     /* Active color */
17167     c = *p++;
17168     if(appData.colorNickNames) {
17169       if( c == appData.colorNickNames[0] ) c = 'w'; else
17170       if( c == appData.colorNickNames[1] ) c = 'b';
17171     }
17172     switch (c) {
17173       case 'w':
17174         *blackPlaysFirst = FALSE;
17175         break;
17176       case 'b':
17177         *blackPlaysFirst = TRUE;
17178         break;
17179       default:
17180         return FALSE;
17181     }
17182
17183     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17184     /* return the extra info in global variiables             */
17185
17186     /* set defaults in case FEN is incomplete */
17187     board[EP_STATUS] = EP_UNKNOWN;
17188     for(i=0; i<nrCastlingRights; i++ ) {
17189         board[CASTLING][i] =
17190             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17191     }   /* assume possible unless obviously impossible */
17192     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17193     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17194     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17195                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17196     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17197     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17198     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17199                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17200     FENrulePlies = 0;
17201
17202     while(*p==' ') p++;
17203     if(nrCastlingRights) {
17204       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17205       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17206           /* castling indicator present, so default becomes no castlings */
17207           for(i=0; i<nrCastlingRights; i++ ) {
17208                  board[CASTLING][i] = NoRights;
17209           }
17210       }
17211       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17212              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17213              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17214              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17215         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17216
17217         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17218             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17219             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17220         }
17221         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17222             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17223         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17224                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17225         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17226                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17227         switch(c) {
17228           case'K':
17229               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17230               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17231               board[CASTLING][2] = whiteKingFile;
17232               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17233               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17234               break;
17235           case'Q':
17236               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17237               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17238               board[CASTLING][2] = whiteKingFile;
17239               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17240               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17241               break;
17242           case'k':
17243               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17244               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17245               board[CASTLING][5] = blackKingFile;
17246               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17247               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17248               break;
17249           case'q':
17250               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17251               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17252               board[CASTLING][5] = blackKingFile;
17253               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17254               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17255           case '-':
17256               break;
17257           default: /* FRC castlings */
17258               if(c >= 'a') { /* black rights */
17259                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17260                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17261                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17262                   if(i == BOARD_RGHT) break;
17263                   board[CASTLING][5] = i;
17264                   c -= AAA;
17265                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17266                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17267                   if(c > i)
17268                       board[CASTLING][3] = c;
17269                   else
17270                       board[CASTLING][4] = c;
17271               } else { /* white rights */
17272                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17273                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17274                     if(board[0][i] == WhiteKing) break;
17275                   if(i == BOARD_RGHT) break;
17276                   board[CASTLING][2] = i;
17277                   c -= AAA - 'a' + 'A';
17278                   if(board[0][c] >= WhiteKing) break;
17279                   if(c > i)
17280                       board[CASTLING][0] = c;
17281                   else
17282                       board[CASTLING][1] = c;
17283               }
17284         }
17285       }
17286       for(i=0; i<nrCastlingRights; i++)
17287         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17288       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17289     if (appData.debugMode) {
17290         fprintf(debugFP, "FEN castling rights:");
17291         for(i=0; i<nrCastlingRights; i++)
17292         fprintf(debugFP, " %d", board[CASTLING][i]);
17293         fprintf(debugFP, "\n");
17294     }
17295
17296       while(*p==' ') p++;
17297     }
17298
17299     /* read e.p. field in games that know e.p. capture */
17300     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17301        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17302       if(*p=='-') {
17303         p++; board[EP_STATUS] = EP_NONE;
17304       } else {
17305          char c = *p++ - AAA;
17306
17307          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17308          if(*p >= '0' && *p <='9') p++;
17309          board[EP_STATUS] = c;
17310       }
17311     }
17312
17313
17314     if(sscanf(p, "%d", &i) == 1) {
17315         FENrulePlies = i; /* 50-move ply counter */
17316         /* (The move number is still ignored)    */
17317     }
17318
17319     return TRUE;
17320 }
17321
17322 void
17323 EditPositionPasteFEN (char *fen)
17324 {
17325   if (fen != NULL) {
17326     Board initial_position;
17327
17328     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17329       DisplayError(_("Bad FEN position in clipboard"), 0);
17330       return ;
17331     } else {
17332       int savedBlackPlaysFirst = blackPlaysFirst;
17333       EditPositionEvent();
17334       blackPlaysFirst = savedBlackPlaysFirst;
17335       CopyBoard(boards[0], initial_position);
17336       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17337       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17338       DisplayBothClocks();
17339       DrawPosition(FALSE, boards[currentMove]);
17340     }
17341   }
17342 }
17343
17344 static char cseq[12] = "\\   ";
17345
17346 Boolean
17347 set_cont_sequence (char *new_seq)
17348 {
17349     int len;
17350     Boolean ret;
17351
17352     // handle bad attempts to set the sequence
17353         if (!new_seq)
17354                 return 0; // acceptable error - no debug
17355
17356     len = strlen(new_seq);
17357     ret = (len > 0) && (len < sizeof(cseq));
17358     if (ret)
17359       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17360     else if (appData.debugMode)
17361       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17362     return ret;
17363 }
17364
17365 /*
17366     reformat a source message so words don't cross the width boundary.  internal
17367     newlines are not removed.  returns the wrapped size (no null character unless
17368     included in source message).  If dest is NULL, only calculate the size required
17369     for the dest buffer.  lp argument indicats line position upon entry, and it's
17370     passed back upon exit.
17371 */
17372 int
17373 wrap (char *dest, char *src, int count, int width, int *lp)
17374 {
17375     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17376
17377     cseq_len = strlen(cseq);
17378     old_line = line = *lp;
17379     ansi = len = clen = 0;
17380
17381     for (i=0; i < count; i++)
17382     {
17383         if (src[i] == '\033')
17384             ansi = 1;
17385
17386         // if we hit the width, back up
17387         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17388         {
17389             // store i & len in case the word is too long
17390             old_i = i, old_len = len;
17391
17392             // find the end of the last word
17393             while (i && src[i] != ' ' && src[i] != '\n')
17394             {
17395                 i--;
17396                 len--;
17397             }
17398
17399             // word too long?  restore i & len before splitting it
17400             if ((old_i-i+clen) >= width)
17401             {
17402                 i = old_i;
17403                 len = old_len;
17404             }
17405
17406             // extra space?
17407             if (i && src[i-1] == ' ')
17408                 len--;
17409
17410             if (src[i] != ' ' && src[i] != '\n')
17411             {
17412                 i--;
17413                 if (len)
17414                     len--;
17415             }
17416
17417             // now append the newline and continuation sequence
17418             if (dest)
17419                 dest[len] = '\n';
17420             len++;
17421             if (dest)
17422                 strncpy(dest+len, cseq, cseq_len);
17423             len += cseq_len;
17424             line = cseq_len;
17425             clen = cseq_len;
17426             continue;
17427         }
17428
17429         if (dest)
17430             dest[len] = src[i];
17431         len++;
17432         if (!ansi)
17433             line++;
17434         if (src[i] == '\n')
17435             line = 0;
17436         if (src[i] == 'm')
17437             ansi = 0;
17438     }
17439     if (dest && appData.debugMode)
17440     {
17441         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17442             count, width, line, len, *lp);
17443         show_bytes(debugFP, src, count);
17444         fprintf(debugFP, "\ndest: ");
17445         show_bytes(debugFP, dest, len);
17446         fprintf(debugFP, "\n");
17447     }
17448     *lp = dest ? line : old_line;
17449
17450     return len;
17451 }
17452
17453 // [HGM] vari: routines for shelving variations
17454 Boolean modeRestore = FALSE;
17455
17456 void
17457 PushInner (int firstMove, int lastMove)
17458 {
17459         int i, j, nrMoves = lastMove - firstMove;
17460
17461         // push current tail of game on stack
17462         savedResult[storedGames] = gameInfo.result;
17463         savedDetails[storedGames] = gameInfo.resultDetails;
17464         gameInfo.resultDetails = NULL;
17465         savedFirst[storedGames] = firstMove;
17466         savedLast [storedGames] = lastMove;
17467         savedFramePtr[storedGames] = framePtr;
17468         framePtr -= nrMoves; // reserve space for the boards
17469         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17470             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17471             for(j=0; j<MOVE_LEN; j++)
17472                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17473             for(j=0; j<2*MOVE_LEN; j++)
17474                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17475             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17476             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17477             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17478             pvInfoList[firstMove+i-1].depth = 0;
17479             commentList[framePtr+i] = commentList[firstMove+i];
17480             commentList[firstMove+i] = NULL;
17481         }
17482
17483         storedGames++;
17484         forwardMostMove = firstMove; // truncate game so we can start variation
17485 }
17486
17487 void
17488 PushTail (int firstMove, int lastMove)
17489 {
17490         if(appData.icsActive) { // only in local mode
17491                 forwardMostMove = currentMove; // mimic old ICS behavior
17492                 return;
17493         }
17494         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17495
17496         PushInner(firstMove, lastMove);
17497         if(storedGames == 1) GreyRevert(FALSE);
17498         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17499 }
17500
17501 void
17502 PopInner (Boolean annotate)
17503 {
17504         int i, j, nrMoves;
17505         char buf[8000], moveBuf[20];
17506
17507         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17508         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17509         nrMoves = savedLast[storedGames] - currentMove;
17510         if(annotate) {
17511                 int cnt = 10;
17512                 if(!WhiteOnMove(currentMove))
17513                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17514                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17515                 for(i=currentMove; i<forwardMostMove; i++) {
17516                         if(WhiteOnMove(i))
17517                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17518                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17519                         strcat(buf, moveBuf);
17520                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17521                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17522                 }
17523                 strcat(buf, ")");
17524         }
17525         for(i=1; i<=nrMoves; i++) { // copy last variation back
17526             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17527             for(j=0; j<MOVE_LEN; j++)
17528                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17529             for(j=0; j<2*MOVE_LEN; j++)
17530                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17531             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17532             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17533             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17534             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17535             commentList[currentMove+i] = commentList[framePtr+i];
17536             commentList[framePtr+i] = NULL;
17537         }
17538         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17539         framePtr = savedFramePtr[storedGames];
17540         gameInfo.result = savedResult[storedGames];
17541         if(gameInfo.resultDetails != NULL) {
17542             free(gameInfo.resultDetails);
17543       }
17544         gameInfo.resultDetails = savedDetails[storedGames];
17545         forwardMostMove = currentMove + nrMoves;
17546 }
17547
17548 Boolean
17549 PopTail (Boolean annotate)
17550 {
17551         if(appData.icsActive) return FALSE; // only in local mode
17552         if(!storedGames) return FALSE; // sanity
17553         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17554
17555         PopInner(annotate);
17556         if(currentMove < forwardMostMove) ForwardEvent(); else
17557         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17558
17559         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17560         return TRUE;
17561 }
17562
17563 void
17564 CleanupTail ()
17565 {       // remove all shelved variations
17566         int i;
17567         for(i=0; i<storedGames; i++) {
17568             if(savedDetails[i])
17569                 free(savedDetails[i]);
17570             savedDetails[i] = NULL;
17571         }
17572         for(i=framePtr; i<MAX_MOVES; i++) {
17573                 if(commentList[i]) free(commentList[i]);
17574                 commentList[i] = NULL;
17575         }
17576         framePtr = MAX_MOVES-1;
17577         storedGames = 0;
17578 }
17579
17580 void
17581 LoadVariation (int index, char *text)
17582 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17583         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17584         int level = 0, move;
17585
17586         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17587         // first find outermost bracketing variation
17588         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17589             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17590                 if(*p == '{') wait = '}'; else
17591                 if(*p == '[') wait = ']'; else
17592                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17593                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17594             }
17595             if(*p == wait) wait = NULLCHAR; // closing ]} found
17596             p++;
17597         }
17598         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17599         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17600         end[1] = NULLCHAR; // clip off comment beyond variation
17601         ToNrEvent(currentMove-1);
17602         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17603         // kludge: use ParsePV() to append variation to game
17604         move = currentMove;
17605         ParsePV(start, TRUE, TRUE);
17606         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17607         ClearPremoveHighlights();
17608         CommentPopDown();
17609         ToNrEvent(currentMove+1);
17610 }
17611
17612 void
17613 LoadTheme ()
17614 {
17615     char *p, *q, buf[MSG_SIZ];
17616     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17617         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17618         ParseArgsFromString(buf);
17619         ActivateTheme(TRUE); // also redo colors
17620         return;
17621     }
17622     p = nickName;
17623     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17624     {
17625         int len;
17626         q = appData.themeNames;
17627         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17628       if(appData.useBitmaps) {
17629         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17630                 appData.liteBackTextureFile, appData.darkBackTextureFile, 
17631                 appData.liteBackTextureMode,
17632                 appData.darkBackTextureMode );
17633       } else {
17634         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17635                 Col2Text(2),   // lightSquareColor
17636                 Col2Text(3) ); // darkSquareColor
17637       }
17638       if(appData.useBorder) {
17639         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17640                 appData.border);
17641       } else {
17642         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17643       }
17644       if(appData.useFont) {
17645         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17646                 appData.renderPiecesWithFont,
17647                 appData.fontToPieceTable,
17648                 Col2Text(9),    // appData.fontBackColorWhite
17649                 Col2Text(10) ); // appData.fontForeColorBlack
17650       } else {
17651         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17652                 appData.pieceDirectory);
17653         if(!appData.pieceDirectory[0])
17654           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17655                 Col2Text(0),   // whitePieceColor
17656                 Col2Text(1) ); // blackPieceColor
17657       }
17658       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17659                 Col2Text(4),   // highlightSquareColor
17660                 Col2Text(5) ); // premoveHighlightColor
17661         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17662         if(insert != q) insert[-1] = NULLCHAR;
17663         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17664         if(q)   free(q);
17665     }
17666     ActivateTheme(FALSE);
17667 }