3ab07f9b79c87bc9c360ca66eb4f8f5d6ec1717c
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 Boolean abortMatch;
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
276
277 /* States for ics_getting_history */
278 #define H_FALSE 0
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
284
285 /* whosays values for GameEnds */
286 #define GE_ICS 0
287 #define GE_ENGINE 1
288 #define GE_PLAYER 2
289 #define GE_FILE 3
290 #define GE_XBOARD 4
291 #define GE_ENGINE1 5
292 #define GE_ENGINE2 6
293
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
296
297 /* Different types of move when calling RegisterMove */
298 #define CMAIL_MOVE   0
299 #define CMAIL_RESIGN 1
300 #define CMAIL_DRAW   2
301 #define CMAIL_ACCEPT 3
302
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
307
308 /* Telnet protocol constants */
309 #define TN_WILL 0373
310 #define TN_WONT 0374
311 #define TN_DO   0375
312 #define TN_DONT 0376
313 #define TN_IAC  0377
314 #define TN_ECHO 0001
315 #define TN_SGA  0003
316 #define TN_PORT 23
317
318 char*
319 safeStrCpy (char *dst, const char *src, size_t count)
320 { // [HGM] made safe
321   int i;
322   assert( dst != NULL );
323   assert( src != NULL );
324   assert( count > 0 );
325
326   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327   if(  i == count && dst[count-1] != NULLCHAR)
328     {
329       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330       if(appData.debugMode)
331       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
332     }
333
334   return dst;
335 }
336
337 /* Some compiler can't cast u64 to double
338  * This function do the job for us:
339
340  * We use the highest bit for cast, this only
341  * works if the highest bit is not
342  * in use (This should not happen)
343  *
344  * We used this for all compiler
345  */
346 double
347 u64ToDouble (u64 value)
348 {
349   double r;
350   u64 tmp = value & u64Const(0x7fffffffffffffff);
351   r = (double)(s64)tmp;
352   if (value & u64Const(0x8000000000000000))
353        r +=  9.2233720368547758080e18; /* 2^63 */
354  return r;
355 }
356
357 /* Fake up flags for now, as we aren't keeping track of castling
358    availability yet. [HGM] Change of logic: the flag now only
359    indicates the type of castlings allowed by the rule of the game.
360    The actual rights themselves are maintained in the array
361    castlingRights, as part of the game history, and are not probed
362    by this function.
363  */
364 int
365 PosFlags (index)
366 {
367   int flags = F_ALL_CASTLE_OK;
368   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369   switch (gameInfo.variant) {
370   case VariantSuicide:
371     flags &= ~F_ALL_CASTLE_OK;
372   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373     flags |= F_IGNORE_CHECK;
374   case VariantLosers:
375     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376     break;
377   case VariantAtomic:
378     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379     break;
380   case VariantKriegspiel:
381     flags |= F_KRIEGSPIEL_CAPTURE;
382     break;
383   case VariantCapaRandom:
384   case VariantFischeRandom:
385     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386   case VariantNoCastle:
387   case VariantShatranj:
388   case VariantCourier:
389   case VariantMakruk:
390   case VariantGrand:
391     flags &= ~F_ALL_CASTLE_OK;
392     break;
393   default:
394     break;
395   }
396   return flags;
397 }
398
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
401
402 /*
403     [AS] Note: sometimes, the sscanf() function is used to parse the input
404     into a fixed-size buffer. Because of this, we must be prepared to
405     receive strings as long as the size of the input buffer, which is currently
406     set to 4K for Windows and 8K for the rest.
407     So, we must either allocate sufficiently large buffers here, or
408     reduce the size of the input buffer in the input reading part.
409 */
410
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
414
415 ChessProgramState first, second, pairing;
416
417 /* premove variables */
418 int premoveToX = 0;
419 int premoveToY = 0;
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
423 int gotPremove = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
426
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
429
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
457
458 int have_sent_ICS_logon = 0;
459 int movesPerSession;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
471
472 /* animateTraining preserves the state of appData.animate
473  * when Training mode is activated. This allows the
474  * response to be animated when appData.animate == TRUE and
475  * appData.animateDragging == TRUE.
476  */
477 Boolean animateTraining;
478
479 GameInfo gameInfo;
480
481 AppData appData;
482
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char  initialRights[BOARD_FILES];
487 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int   initialRulePlies, FENrulePlies;
489 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
490 int loadFlag = 0;
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
493
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
497 int storedGames = 0;
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
503
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
509
510 ChessSquare  FIDEArray[2][BOARD_FILES] = {
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514         BlackKing, BlackBishop, BlackKnight, BlackRook }
515 };
516
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackKing, BlackKnight, BlackRook }
522 };
523
524 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527     { BlackRook, BlackMan, BlackBishop, BlackQueen,
528         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 };
530
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 };
537
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 };
544
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 };
551
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackMan, BlackFerz,
556         BlackKing, BlackMan, BlackKnight, BlackRook }
557 };
558
559
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 };
567
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 };
574
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 };
581
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 };
588
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 };
595
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 };
602
603 #ifdef GOTHIC
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 };
610 #else // !GOTHIC
611 #define GothicArray CapablancaArray
612 #endif // !GOTHIC
613
614 #ifdef FALCON
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
620 };
621 #else // !FALCON
622 #define FalconArray CapablancaArray
623 #endif // !FALCON
624
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
631
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
638 };
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
642
643
644 Board initialPosition;
645
646
647 /* Convert str to a rating. Checks for special cases of "----",
648
649    "++++", etc. Also strips ()'s */
650 int
651 string_to_rating (char *str)
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats ()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit ()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine (ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions (ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("first"),
744   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 N_("second")
747 };
748
749 void
750 InitEngine (ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789
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 || controlKey) && 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(v == VariantKnightmate) {
7618                 if(nMine == 1) return FALSE;
7619                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7620         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7621                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7622
7623                 if(nMine == 1) return FALSE; // bare King
7624                 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
7625                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7626                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7627                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7628                 if(pCnt[WhiteKnight+side])
7629                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7630                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7631                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7632                 if(nBishops)
7633                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7634                 if(pCnt[WhiteAlfil+side])
7635                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7636                 if(pCnt[WhiteWazir+side])
7637                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7638         }
7639
7640         return TRUE;
7641 }
7642
7643 int
7644 CompareWithRights (Board b1, Board b2)
7645 {
7646     int rights = 0;
7647     if(!CompareBoards(b1, b2)) return FALSE;
7648     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7649     /* compare castling rights */
7650     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7651            rights++; /* King lost rights, while rook still had them */
7652     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7653         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7654            rights++; /* but at least one rook lost them */
7655     }
7656     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7657            rights++;
7658     if( b1[CASTLING][5] != NoRights ) {
7659         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7660            rights++;
7661     }
7662     return rights == 0;
7663 }
7664
7665 int
7666 Adjudicate (ChessProgramState *cps)
7667 {       // [HGM] some adjudications useful with buggy engines
7668         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7669         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7670         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7671         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7672         int k, drop, count = 0; static int bare = 1;
7673         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7674         Boolean canAdjudicate = !appData.icsActive;
7675
7676         // most tests only when we understand the game, i.e. legality-checking on
7677             if( appData.testLegality )
7678             {   /* [HGM] Some more adjudications for obstinate engines */
7679                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7680                 static int moveCount = 6;
7681                 ChessMove result;
7682                 char *reason = NULL;
7683
7684                 /* Count what is on board. */
7685                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7686
7687                 /* Some material-based adjudications that have to be made before stalemate test */
7688                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7689                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7690                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7691                      if(canAdjudicate && appData.checkMates) {
7692                          if(engineOpponent)
7693                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7694                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7695                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7696                          return 1;
7697                      }
7698                 }
7699
7700                 /* Bare King in Shatranj (loses) or Losers (wins) */
7701                 if( nrW == 1 || nrB == 1) {
7702                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7703                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7704                      if(canAdjudicate && appData.checkMates) {
7705                          if(engineOpponent)
7706                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7707                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7708                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7709                          return 1;
7710                      }
7711                   } else
7712                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7713                   {    /* bare King */
7714                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7715                         if(canAdjudicate && appData.checkMates) {
7716                             /* but only adjudicate if adjudication enabled */
7717                             if(engineOpponent)
7718                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7719                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7720                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7721                             return 1;
7722                         }
7723                   }
7724                 } else bare = 1;
7725
7726
7727             // don't wait for engine to announce game end if we can judge ourselves
7728             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7729               case MT_CHECK:
7730                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7731                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7732                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7733                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7734                             checkCnt++;
7735                         if(checkCnt >= 2) {
7736                             reason = "Xboard adjudication: 3rd check";
7737                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7738                             break;
7739                         }
7740                     }
7741                 }
7742               case MT_NONE:
7743               default:
7744                 break;
7745               case MT_STALEMATE:
7746               case MT_STAINMATE:
7747                 reason = "Xboard adjudication: Stalemate";
7748                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7749                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7750                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7751                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7752                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7753                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7754                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7755                                                                         EP_CHECKMATE : EP_WINS);
7756                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7757                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7758                 }
7759                 break;
7760               case MT_CHECKMATE:
7761                 reason = "Xboard adjudication: Checkmate";
7762                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7763                 break;
7764             }
7765
7766                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7767                     case EP_STALEMATE:
7768                         result = GameIsDrawn; break;
7769                     case EP_CHECKMATE:
7770                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7771                     case EP_WINS:
7772                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7773                     default:
7774                         result = EndOfFile;
7775                 }
7776                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7777                     if(engineOpponent)
7778                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7779                     GameEnds( result, reason, GE_XBOARD );
7780                     return 1;
7781                 }
7782
7783                 /* Next absolutely insufficient mating material. */
7784                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7785                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7786                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7787
7788                      /* always flag draws, for judging claims */
7789                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7790
7791                      if(canAdjudicate && appData.materialDraws) {
7792                          /* but only adjudicate them if adjudication enabled */
7793                          if(engineOpponent) {
7794                            SendToProgram("force\n", engineOpponent); // suppress reply
7795                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7796                          }
7797                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7798                          return 1;
7799                      }
7800                 }
7801
7802                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7803                 if(gameInfo.variant == VariantXiangqi ?
7804                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7805                  : nrW + nrB == 4 &&
7806                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7807                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7808                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7809                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7810                    ) ) {
7811                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7812                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7813                           if(engineOpponent) {
7814                             SendToProgram("force\n", engineOpponent); // suppress reply
7815                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7816                           }
7817                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7818                           return 1;
7819                      }
7820                 } else moveCount = 6;
7821             }
7822
7823         // Repetition draws and 50-move rule can be applied independently of legality testing
7824
7825                 /* Check for rep-draws */
7826                 count = 0;
7827                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7828                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7829                 for(k = forwardMostMove-2;
7830                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7831                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7832                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7833                     k-=2)
7834                 {   int rights=0;
7835                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7836                         /* compare castling rights */
7837                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7838                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7839                                 rights++; /* King lost rights, while rook still had them */
7840                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7841                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7842                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7843                                    rights++; /* but at least one rook lost them */
7844                         }
7845                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7846                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7847                                 rights++;
7848                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7849                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7850                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7851                                    rights++;
7852                         }
7853                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7854                             && appData.drawRepeats > 1) {
7855                              /* adjudicate after user-specified nr of repeats */
7856                              int result = GameIsDrawn;
7857                              char *details = "XBoard adjudication: repetition draw";
7858                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7859                                 // [HGM] xiangqi: check for forbidden perpetuals
7860                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7861                                 for(m=forwardMostMove; m>k; m-=2) {
7862                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7863                                         ourPerpetual = 0; // the current mover did not always check
7864                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7865                                         hisPerpetual = 0; // the opponent did not always check
7866                                 }
7867                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7868                                                                         ourPerpetual, hisPerpetual);
7869                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7870                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7871                                     details = "Xboard adjudication: perpetual checking";
7872                                 } else
7873                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7874                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7875                                 } else
7876                                 // Now check for perpetual chases
7877                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7878                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7879                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7880                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7881                                         static char resdet[MSG_SIZ];
7882                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7883                                         details = resdet;
7884                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7885                                     } else
7886                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7887                                         break; // Abort repetition-checking loop.
7888                                 }
7889                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7890                              }
7891                              if(engineOpponent) {
7892                                SendToProgram("force\n", engineOpponent); // suppress reply
7893                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7894                              }
7895                              GameEnds( result, details, GE_XBOARD );
7896                              return 1;
7897                         }
7898                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7899                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7900                     }
7901                 }
7902
7903                 /* Now we test for 50-move draws. Determine ply count */
7904                 count = forwardMostMove;
7905                 /* look for last irreversble move */
7906                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7907                     count--;
7908                 /* if we hit starting position, add initial plies */
7909                 if( count == backwardMostMove )
7910                     count -= initialRulePlies;
7911                 count = forwardMostMove - count;
7912                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7913                         // adjust reversible move counter for checks in Xiangqi
7914                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7915                         if(i < backwardMostMove) i = backwardMostMove;
7916                         while(i <= forwardMostMove) {
7917                                 lastCheck = inCheck; // check evasion does not count
7918                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7919                                 if(inCheck || lastCheck) count--; // check does not count
7920                                 i++;
7921                         }
7922                 }
7923                 if( count >= 100)
7924                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7925                          /* this is used to judge if draw claims are legal */
7926                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7927                          if(engineOpponent) {
7928                            SendToProgram("force\n", engineOpponent); // suppress reply
7929                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7930                          }
7931                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7932                          return 1;
7933                 }
7934
7935                 /* if draw offer is pending, treat it as a draw claim
7936                  * when draw condition present, to allow engines a way to
7937                  * claim draws before making their move to avoid a race
7938                  * condition occurring after their move
7939                  */
7940                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7941                          char *p = NULL;
7942                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7943                              p = "Draw claim: 50-move rule";
7944                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7945                              p = "Draw claim: 3-fold repetition";
7946                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7947                              p = "Draw claim: insufficient mating material";
7948                          if( p != NULL && canAdjudicate) {
7949                              if(engineOpponent) {
7950                                SendToProgram("force\n", engineOpponent); // suppress reply
7951                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7952                              }
7953                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7954                              return 1;
7955                          }
7956                 }
7957
7958                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7959                     if(engineOpponent) {
7960                       SendToProgram("force\n", engineOpponent); // suppress reply
7961                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7962                     }
7963                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7964                     return 1;
7965                 }
7966         return 0;
7967 }
7968
7969 char *
7970 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7971 {   // [HGM] book: this routine intercepts moves to simulate book replies
7972     char *bookHit = NULL;
7973
7974     //first determine if the incoming move brings opponent into his book
7975     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7976         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7977     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7978     if(bookHit != NULL && !cps->bookSuspend) {
7979         // make sure opponent is not going to reply after receiving move to book position
7980         SendToProgram("force\n", cps);
7981         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7982     }
7983     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7984     // now arrange restart after book miss
7985     if(bookHit) {
7986         // after a book hit we never send 'go', and the code after the call to this routine
7987         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7988         char buf[MSG_SIZ], *move = bookHit;
7989         if(cps->useSAN) {
7990             int fromX, fromY, toX, toY;
7991             char promoChar;
7992             ChessMove moveType;
7993             move = buf + 30;
7994             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7995                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7996                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7997                                     PosFlags(forwardMostMove),
7998                                     fromY, fromX, toY, toX, promoChar, move);
7999             } else {
8000                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8001                 bookHit = NULL;
8002             }
8003         }
8004         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8005         SendToProgram(buf, cps);
8006         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8007     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8008         SendToProgram("go\n", cps);
8009         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8010     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8011         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8012             SendToProgram("go\n", cps);
8013         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8014     }
8015     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8016 }
8017
8018 int
8019 LoadError (char *errmess, ChessProgramState *cps)
8020 {   // unloads engine and switches back to -ncp mode if it was first
8021     if(cps->initDone) return FALSE;
8022     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8023     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8024     cps->pr = NoProc;
8025     if(cps == &first) {
8026         appData.noChessProgram = TRUE;
8027         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8028         gameMode = BeginningOfGame; ModeHighlight();
8029         SetNCPMode();
8030     }
8031     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8032     DisplayMessage("", ""); // erase waiting message
8033     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8034     return TRUE;
8035 }
8036
8037 char *savedMessage;
8038 ChessProgramState *savedState;
8039 void
8040 DeferredBookMove (void)
8041 {
8042         if(savedState->lastPing != savedState->lastPong)
8043                     ScheduleDelayedEvent(DeferredBookMove, 10);
8044         else
8045         HandleMachineMove(savedMessage, savedState);
8046 }
8047
8048 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8049 static ChessProgramState *stalledEngine;
8050 static char stashedInputMove[MSG_SIZ];
8051
8052 void
8053 HandleMachineMove (char *message, ChessProgramState *cps)
8054 {
8055     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8056     char realname[MSG_SIZ];
8057     int fromX, fromY, toX, toY;
8058     ChessMove moveType;
8059     char promoChar;
8060     char *p, *pv=buf1;
8061     int machineWhite, oldError;
8062     char *bookHit;
8063
8064     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8065         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8066         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8067             DisplayError(_("Invalid pairing from pairing engine"), 0);
8068             return;
8069         }
8070         pairingReceived = 1;
8071         NextMatchGame();
8072         return; // Skim the pairing messages here.
8073     }
8074
8075     oldError = cps->userError; cps->userError = 0;
8076
8077 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8078     /*
8079      * Kludge to ignore BEL characters
8080      */
8081     while (*message == '\007') message++;
8082
8083     /*
8084      * [HGM] engine debug message: ignore lines starting with '#' character
8085      */
8086     if(cps->debug && *message == '#') return;
8087
8088     /*
8089      * Look for book output
8090      */
8091     if (cps == &first && bookRequested) {
8092         if (message[0] == '\t' || message[0] == ' ') {
8093             /* Part of the book output is here; append it */
8094             strcat(bookOutput, message);
8095             strcat(bookOutput, "  \n");
8096             return;
8097         } else if (bookOutput[0] != NULLCHAR) {
8098             /* All of book output has arrived; display it */
8099             char *p = bookOutput;
8100             while (*p != NULLCHAR) {
8101                 if (*p == '\t') *p = ' ';
8102                 p++;
8103             }
8104             DisplayInformation(bookOutput);
8105             bookRequested = FALSE;
8106             /* Fall through to parse the current output */
8107         }
8108     }
8109
8110     /*
8111      * Look for machine move.
8112      */
8113     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8114         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8115     {
8116         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8117             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8118             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8119             stalledEngine = cps;
8120             if(appData.ponderNextMove) { // bring opponent out of ponder
8121                 if(gameMode == TwoMachinesPlay) {
8122                     if(cps->other->pause)
8123                         PauseEngine(cps->other);
8124                     else
8125                         SendToProgram("easy\n", cps->other);
8126                 }
8127             }
8128             StopClocks();
8129             return;
8130         }
8131
8132         /* This method is only useful on engines that support ping */
8133         if (cps->lastPing != cps->lastPong) {
8134           if (gameMode == BeginningOfGame) {
8135             /* Extra move from before last new; ignore */
8136             if (appData.debugMode) {
8137                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8138             }
8139           } else {
8140             if (appData.debugMode) {
8141                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8142                         cps->which, gameMode);
8143             }
8144
8145             SendToProgram("undo\n", cps);
8146           }
8147           return;
8148         }
8149
8150         switch (gameMode) {
8151           case BeginningOfGame:
8152             /* Extra move from before last reset; ignore */
8153             if (appData.debugMode) {
8154                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8155             }
8156             return;
8157
8158           case EndOfGame:
8159           case IcsIdle:
8160           default:
8161             /* Extra move after we tried to stop.  The mode test is
8162                not a reliable way of detecting this problem, but it's
8163                the best we can do on engines that don't support ping.
8164             */
8165             if (appData.debugMode) {
8166                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8167                         cps->which, gameMode);
8168             }
8169             SendToProgram("undo\n", cps);
8170             return;
8171
8172           case MachinePlaysWhite:
8173           case IcsPlayingWhite:
8174             machineWhite = TRUE;
8175             break;
8176
8177           case MachinePlaysBlack:
8178           case IcsPlayingBlack:
8179             machineWhite = FALSE;
8180             break;
8181
8182           case TwoMachinesPlay:
8183             machineWhite = (cps->twoMachinesColor[0] == 'w');
8184             break;
8185         }
8186         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8187             if (appData.debugMode) {
8188                 fprintf(debugFP,
8189                         "Ignoring move out of turn by %s, gameMode %d"
8190                         ", forwardMost %d\n",
8191                         cps->which, gameMode, forwardMostMove);
8192             }
8193             return;
8194         }
8195
8196         if(cps->alphaRank) AlphaRank(machineMove, 4);
8197         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8198                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8199             /* Machine move could not be parsed; ignore it. */
8200           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8201                     machineMove, _(cps->which));
8202             DisplayError(buf1, 0);
8203             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8204                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8205             if (gameMode == TwoMachinesPlay) {
8206               GameEnds(machineWhite ? BlackWins : WhiteWins,
8207                        buf1, GE_XBOARD);
8208             }
8209             return;
8210         }
8211
8212         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8213         /* So we have to redo legality test with true e.p. status here,  */
8214         /* to make sure an illegal e.p. capture does not slip through,   */
8215         /* to cause a forfeit on a justified illegal-move complaint      */
8216         /* of the opponent.                                              */
8217         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8218            ChessMove moveType;
8219            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8220                              fromY, fromX, toY, toX, promoChar);
8221             if(moveType == IllegalMove) {
8222               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8223                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8224                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8225                            buf1, GE_XBOARD);
8226                 return;
8227            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8228            /* [HGM] Kludge to handle engines that send FRC-style castling
8229               when they shouldn't (like TSCP-Gothic) */
8230            switch(moveType) {
8231              case WhiteASideCastleFR:
8232              case BlackASideCastleFR:
8233                toX+=2;
8234                currentMoveString[2]++;
8235                break;
8236              case WhiteHSideCastleFR:
8237              case BlackHSideCastleFR:
8238                toX--;
8239                currentMoveString[2]--;
8240                break;
8241              default: ; // nothing to do, but suppresses warning of pedantic compilers
8242            }
8243         }
8244         hintRequested = FALSE;
8245         lastHint[0] = NULLCHAR;
8246         bookRequested = FALSE;
8247         /* Program may be pondering now */
8248         cps->maybeThinking = TRUE;
8249         if (cps->sendTime == 2) cps->sendTime = 1;
8250         if (cps->offeredDraw) cps->offeredDraw--;
8251
8252         /* [AS] Save move info*/
8253         pvInfoList[ forwardMostMove ].score = programStats.score;
8254         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8255         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8256
8257         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8258
8259         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8260         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8261             int count = 0;
8262
8263             while( count < adjudicateLossPlies ) {
8264                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8265
8266                 if( count & 1 ) {
8267                     score = -score; /* Flip score for winning side */
8268                 }
8269
8270                 if( score > adjudicateLossThreshold ) {
8271                     break;
8272                 }
8273
8274                 count++;
8275             }
8276
8277             if( count >= adjudicateLossPlies ) {
8278                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8279
8280                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8281                     "Xboard adjudication",
8282                     GE_XBOARD );
8283
8284                 return;
8285             }
8286         }
8287
8288         if(Adjudicate(cps)) {
8289             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8290             return; // [HGM] adjudicate: for all automatic game ends
8291         }
8292
8293 #if ZIPPY
8294         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8295             first.initDone) {
8296           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8297                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8298                 SendToICS("draw ");
8299                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8300           }
8301           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8302           ics_user_moved = 1;
8303           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8304                 char buf[3*MSG_SIZ];
8305
8306                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8307                         programStats.score / 100.,
8308                         programStats.depth,
8309                         programStats.time / 100.,
8310                         (unsigned int)programStats.nodes,
8311                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8312                         programStats.movelist);
8313                 SendToICS(buf);
8314 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8315           }
8316         }
8317 #endif
8318
8319         /* [AS] Clear stats for next move */
8320         ClearProgramStats();
8321         thinkOutput[0] = NULLCHAR;
8322         hiddenThinkOutputState = 0;
8323
8324         bookHit = NULL;
8325         if (gameMode == TwoMachinesPlay) {
8326             /* [HGM] relaying draw offers moved to after reception of move */
8327             /* and interpreting offer as claim if it brings draw condition */
8328             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8329                 SendToProgram("draw\n", cps->other);
8330             }
8331             if (cps->other->sendTime) {
8332                 SendTimeRemaining(cps->other,
8333                                   cps->other->twoMachinesColor[0] == 'w');
8334             }
8335             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8336             if (firstMove && !bookHit) {
8337                 firstMove = FALSE;
8338                 if (cps->other->useColors) {
8339                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8340                 }
8341                 SendToProgram("go\n", cps->other);
8342             }
8343             cps->other->maybeThinking = TRUE;
8344         }
8345
8346         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8347
8348         if (!pausing && appData.ringBellAfterMoves) {
8349             RingBell();
8350         }
8351
8352         /*
8353          * Reenable menu items that were disabled while
8354          * machine was thinking
8355          */
8356         if (gameMode != TwoMachinesPlay)
8357             SetUserThinkingEnables();
8358
8359         // [HGM] book: after book hit opponent has received move and is now in force mode
8360         // force the book reply into it, and then fake that it outputted this move by jumping
8361         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8362         if(bookHit) {
8363                 static char bookMove[MSG_SIZ]; // a bit generous?
8364
8365                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8366                 strcat(bookMove, bookHit);
8367                 message = bookMove;
8368                 cps = cps->other;
8369                 programStats.nodes = programStats.depth = programStats.time =
8370                 programStats.score = programStats.got_only_move = 0;
8371                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8372
8373                 if(cps->lastPing != cps->lastPong) {
8374                     savedMessage = message; // args for deferred call
8375                     savedState = cps;
8376                     ScheduleDelayedEvent(DeferredBookMove, 10);
8377                     return;
8378                 }
8379                 goto FakeBookMove;
8380         }
8381
8382         return;
8383     }
8384
8385     /* Set special modes for chess engines.  Later something general
8386      *  could be added here; for now there is just one kludge feature,
8387      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8388      *  when "xboard" is given as an interactive command.
8389      */
8390     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8391         cps->useSigint = FALSE;
8392         cps->useSigterm = FALSE;
8393     }
8394     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8395       ParseFeatures(message+8, cps);
8396       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8397     }
8398
8399     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8400                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8401       int dummy, s=6; char buf[MSG_SIZ];
8402       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8403       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8404       if(startedFromSetupPosition) return;
8405       ParseFEN(boards[0], &dummy, message+s);
8406       DrawPosition(TRUE, boards[0]);
8407       startedFromSetupPosition = TRUE;
8408       return;
8409     }
8410     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8411      * want this, I was asked to put it in, and obliged.
8412      */
8413     if (!strncmp(message, "setboard ", 9)) {
8414         Board initial_position;
8415
8416         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8417
8418         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8419             DisplayError(_("Bad FEN received from engine"), 0);
8420             return ;
8421         } else {
8422            Reset(TRUE, FALSE);
8423            CopyBoard(boards[0], initial_position);
8424            initialRulePlies = FENrulePlies;
8425            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8426            else gameMode = MachinePlaysBlack;
8427            DrawPosition(FALSE, boards[currentMove]);
8428         }
8429         return;
8430     }
8431
8432     /*
8433      * Look for communication commands
8434      */
8435     if (!strncmp(message, "telluser ", 9)) {
8436         if(message[9] == '\\' && message[10] == '\\')
8437             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8438         PlayTellSound();
8439         DisplayNote(message + 9);
8440         return;
8441     }
8442     if (!strncmp(message, "tellusererror ", 14)) {
8443         cps->userError = 1;
8444         if(message[14] == '\\' && message[15] == '\\')
8445             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8446         PlayTellSound();
8447         DisplayError(message + 14, 0);
8448         return;
8449     }
8450     if (!strncmp(message, "tellopponent ", 13)) {
8451       if (appData.icsActive) {
8452         if (loggedOn) {
8453           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8454           SendToICS(buf1);
8455         }
8456       } else {
8457         DisplayNote(message + 13);
8458       }
8459       return;
8460     }
8461     if (!strncmp(message, "tellothers ", 11)) {
8462       if (appData.icsActive) {
8463         if (loggedOn) {
8464           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8465           SendToICS(buf1);
8466         }
8467       }
8468       return;
8469     }
8470     if (!strncmp(message, "tellall ", 8)) {
8471       if (appData.icsActive) {
8472         if (loggedOn) {
8473           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8474           SendToICS(buf1);
8475         }
8476       } else {
8477         DisplayNote(message + 8);
8478       }
8479       return;
8480     }
8481     if (strncmp(message, "warning", 7) == 0) {
8482         /* Undocumented feature, use tellusererror in new code */
8483         DisplayError(message, 0);
8484         return;
8485     }
8486     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8487         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8488         strcat(realname, " query");
8489         AskQuestion(realname, buf2, buf1, cps->pr);
8490         return;
8491     }
8492     /* Commands from the engine directly to ICS.  We don't allow these to be
8493      *  sent until we are logged on. Crafty kibitzes have been known to
8494      *  interfere with the login process.
8495      */
8496     if (loggedOn) {
8497         if (!strncmp(message, "tellics ", 8)) {
8498             SendToICS(message + 8);
8499             SendToICS("\n");
8500             return;
8501         }
8502         if (!strncmp(message, "tellicsnoalias ", 15)) {
8503             SendToICS(ics_prefix);
8504             SendToICS(message + 15);
8505             SendToICS("\n");
8506             return;
8507         }
8508         /* The following are for backward compatibility only */
8509         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8510             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8511             SendToICS(ics_prefix);
8512             SendToICS(message);
8513             SendToICS("\n");
8514             return;
8515         }
8516     }
8517     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8518         return;
8519     }
8520     /*
8521      * If the move is illegal, cancel it and redraw the board.
8522      * Also deal with other error cases.  Matching is rather loose
8523      * here to accommodate engines written before the spec.
8524      */
8525     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8526         strncmp(message, "Error", 5) == 0) {
8527         if (StrStr(message, "name") ||
8528             StrStr(message, "rating") || StrStr(message, "?") ||
8529             StrStr(message, "result") || StrStr(message, "board") ||
8530             StrStr(message, "bk") || StrStr(message, "computer") ||
8531             StrStr(message, "variant") || StrStr(message, "hint") ||
8532             StrStr(message, "random") || StrStr(message, "depth") ||
8533             StrStr(message, "accepted")) {
8534             return;
8535         }
8536         if (StrStr(message, "protover")) {
8537           /* Program is responding to input, so it's apparently done
8538              initializing, and this error message indicates it is
8539              protocol version 1.  So we don't need to wait any longer
8540              for it to initialize and send feature commands. */
8541           FeatureDone(cps, 1);
8542           cps->protocolVersion = 1;
8543           return;
8544         }
8545         cps->maybeThinking = FALSE;
8546
8547         if (StrStr(message, "draw")) {
8548             /* Program doesn't have "draw" command */
8549             cps->sendDrawOffers = 0;
8550             return;
8551         }
8552         if (cps->sendTime != 1 &&
8553             (StrStr(message, "time") || StrStr(message, "otim"))) {
8554           /* Program apparently doesn't have "time" or "otim" command */
8555           cps->sendTime = 0;
8556           return;
8557         }
8558         if (StrStr(message, "analyze")) {
8559             cps->analysisSupport = FALSE;
8560             cps->analyzing = FALSE;
8561 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8562             EditGameEvent(); // [HGM] try to preserve loaded game
8563             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8564             DisplayError(buf2, 0);
8565             return;
8566         }
8567         if (StrStr(message, "(no matching move)st")) {
8568           /* Special kludge for GNU Chess 4 only */
8569           cps->stKludge = TRUE;
8570           SendTimeControl(cps, movesPerSession, timeControl,
8571                           timeIncrement, appData.searchDepth,
8572                           searchTime);
8573           return;
8574         }
8575         if (StrStr(message, "(no matching move)sd")) {
8576           /* Special kludge for GNU Chess 4 only */
8577           cps->sdKludge = TRUE;
8578           SendTimeControl(cps, movesPerSession, timeControl,
8579                           timeIncrement, appData.searchDepth,
8580                           searchTime);
8581           return;
8582         }
8583         if (!StrStr(message, "llegal")) {
8584             return;
8585         }
8586         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8587             gameMode == IcsIdle) return;
8588         if (forwardMostMove <= backwardMostMove) return;
8589         if (pausing) PauseEvent();
8590       if(appData.forceIllegal) {
8591             // [HGM] illegal: machine refused move; force position after move into it
8592           SendToProgram("force\n", cps);
8593           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8594                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8595                 // when black is to move, while there might be nothing on a2 or black
8596                 // might already have the move. So send the board as if white has the move.
8597                 // But first we must change the stm of the engine, as it refused the last move
8598                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8599                 if(WhiteOnMove(forwardMostMove)) {
8600                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8601                     SendBoard(cps, forwardMostMove); // kludgeless board
8602                 } else {
8603                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8604                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8605                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8606                 }
8607           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8608             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8609                  gameMode == TwoMachinesPlay)
8610               SendToProgram("go\n", cps);
8611             return;
8612       } else
8613         if (gameMode == PlayFromGameFile) {
8614             /* Stop reading this game file */
8615             gameMode = EditGame;
8616             ModeHighlight();
8617         }
8618         /* [HGM] illegal-move claim should forfeit game when Xboard */
8619         /* only passes fully legal moves                            */
8620         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8621             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8622                                 "False illegal-move claim", GE_XBOARD );
8623             return; // do not take back move we tested as valid
8624         }
8625         currentMove = forwardMostMove-1;
8626         DisplayMove(currentMove-1); /* before DisplayMoveError */
8627         SwitchClocks(forwardMostMove-1); // [HGM] race
8628         DisplayBothClocks();
8629         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8630                 parseList[currentMove], _(cps->which));
8631         DisplayMoveError(buf1);
8632         DrawPosition(FALSE, boards[currentMove]);
8633
8634         SetUserThinkingEnables();
8635         return;
8636     }
8637     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8638         /* Program has a broken "time" command that
8639            outputs a string not ending in newline.
8640            Don't use it. */
8641         cps->sendTime = 0;
8642     }
8643
8644     /*
8645      * If chess program startup fails, exit with an error message.
8646      * Attempts to recover here are futile. [HGM] Well, we try anyway
8647      */
8648     if ((StrStr(message, "unknown host") != NULL)
8649         || (StrStr(message, "No remote directory") != NULL)
8650         || (StrStr(message, "not found") != NULL)
8651         || (StrStr(message, "No such file") != NULL)
8652         || (StrStr(message, "can't alloc") != NULL)
8653         || (StrStr(message, "Permission denied") != NULL)) {
8654
8655         cps->maybeThinking = FALSE;
8656         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8657                 _(cps->which), cps->program, cps->host, message);
8658         RemoveInputSource(cps->isr);
8659         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8660             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8661             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8662         }
8663         return;
8664     }
8665
8666     /*
8667      * Look for hint output
8668      */
8669     if (sscanf(message, "Hint: %s", buf1) == 1) {
8670         if (cps == &first && hintRequested) {
8671             hintRequested = FALSE;
8672             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8673                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8674                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8675                                     PosFlags(forwardMostMove),
8676                                     fromY, fromX, toY, toX, promoChar, buf1);
8677                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8678                 DisplayInformation(buf2);
8679             } else {
8680                 /* Hint move could not be parsed!? */
8681               snprintf(buf2, sizeof(buf2),
8682                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8683                         buf1, _(cps->which));
8684                 DisplayError(buf2, 0);
8685             }
8686         } else {
8687           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8688         }
8689         return;
8690     }
8691
8692     /*
8693      * Ignore other messages if game is not in progress
8694      */
8695     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8696         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8697
8698     /*
8699      * look for win, lose, draw, or draw offer
8700      */
8701     if (strncmp(message, "1-0", 3) == 0) {
8702         char *p, *q, *r = "";
8703         p = strchr(message, '{');
8704         if (p) {
8705             q = strchr(p, '}');
8706             if (q) {
8707                 *q = NULLCHAR;
8708                 r = p + 1;
8709             }
8710         }
8711         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8712         return;
8713     } else if (strncmp(message, "0-1", 3) == 0) {
8714         char *p, *q, *r = "";
8715         p = strchr(message, '{');
8716         if (p) {
8717             q = strchr(p, '}');
8718             if (q) {
8719                 *q = NULLCHAR;
8720                 r = p + 1;
8721             }
8722         }
8723         /* Kludge for Arasan 4.1 bug */
8724         if (strcmp(r, "Black resigns") == 0) {
8725             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8726             return;
8727         }
8728         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8729         return;
8730     } else if (strncmp(message, "1/2", 3) == 0) {
8731         char *p, *q, *r = "";
8732         p = strchr(message, '{');
8733         if (p) {
8734             q = strchr(p, '}');
8735             if (q) {
8736                 *q = NULLCHAR;
8737                 r = p + 1;
8738             }
8739         }
8740
8741         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8742         return;
8743
8744     } else if (strncmp(message, "White resign", 12) == 0) {
8745         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8746         return;
8747     } else if (strncmp(message, "Black resign", 12) == 0) {
8748         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8749         return;
8750     } else if (strncmp(message, "White matches", 13) == 0 ||
8751                strncmp(message, "Black matches", 13) == 0   ) {
8752         /* [HGM] ignore GNUShogi noises */
8753         return;
8754     } else if (strncmp(message, "White", 5) == 0 &&
8755                message[5] != '(' &&
8756                StrStr(message, "Black") == NULL) {
8757         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8758         return;
8759     } else if (strncmp(message, "Black", 5) == 0 &&
8760                message[5] != '(') {
8761         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8762         return;
8763     } else if (strcmp(message, "resign") == 0 ||
8764                strcmp(message, "computer resigns") == 0) {
8765         switch (gameMode) {
8766           case MachinePlaysBlack:
8767           case IcsPlayingBlack:
8768             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8769             break;
8770           case MachinePlaysWhite:
8771           case IcsPlayingWhite:
8772             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8773             break;
8774           case TwoMachinesPlay:
8775             if (cps->twoMachinesColor[0] == 'w')
8776               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8777             else
8778               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8779             break;
8780           default:
8781             /* can't happen */
8782             break;
8783         }
8784         return;
8785     } else if (strncmp(message, "opponent mates", 14) == 0) {
8786         switch (gameMode) {
8787           case MachinePlaysBlack:
8788           case IcsPlayingBlack:
8789             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8790             break;
8791           case MachinePlaysWhite:
8792           case IcsPlayingWhite:
8793             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8794             break;
8795           case TwoMachinesPlay:
8796             if (cps->twoMachinesColor[0] == 'w')
8797               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8798             else
8799               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8800             break;
8801           default:
8802             /* can't happen */
8803             break;
8804         }
8805         return;
8806     } else if (strncmp(message, "computer mates", 14) == 0) {
8807         switch (gameMode) {
8808           case MachinePlaysBlack:
8809           case IcsPlayingBlack:
8810             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8811             break;
8812           case MachinePlaysWhite:
8813           case IcsPlayingWhite:
8814             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8815             break;
8816           case TwoMachinesPlay:
8817             if (cps->twoMachinesColor[0] == 'w')
8818               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8819             else
8820               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8821             break;
8822           default:
8823             /* can't happen */
8824             break;
8825         }
8826         return;
8827     } else if (strncmp(message, "checkmate", 9) == 0) {
8828         if (WhiteOnMove(forwardMostMove)) {
8829             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8830         } else {
8831             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8832         }
8833         return;
8834     } else if (strstr(message, "Draw") != NULL ||
8835                strstr(message, "game is a draw") != NULL) {
8836         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8837         return;
8838     } else if (strstr(message, "offer") != NULL &&
8839                strstr(message, "draw") != NULL) {
8840 #if ZIPPY
8841         if (appData.zippyPlay && first.initDone) {
8842             /* Relay offer to ICS */
8843             SendToICS(ics_prefix);
8844             SendToICS("draw\n");
8845         }
8846 #endif
8847         cps->offeredDraw = 2; /* valid until this engine moves twice */
8848         if (gameMode == TwoMachinesPlay) {
8849             if (cps->other->offeredDraw) {
8850                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8851             /* [HGM] in two-machine mode we delay relaying draw offer      */
8852             /* until after we also have move, to see if it is really claim */
8853             }
8854         } else if (gameMode == MachinePlaysWhite ||
8855                    gameMode == MachinePlaysBlack) {
8856           if (userOfferedDraw) {
8857             DisplayInformation(_("Machine accepts your draw offer"));
8858             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8859           } else {
8860             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8861           }
8862         }
8863     }
8864
8865
8866     /*
8867      * Look for thinking output
8868      */
8869     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8870           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8871                                 ) {
8872         int plylev, mvleft, mvtot, curscore, time;
8873         char mvname[MOVE_LEN];
8874         u64 nodes; // [DM]
8875         char plyext;
8876         int ignore = FALSE;
8877         int prefixHint = FALSE;
8878         mvname[0] = NULLCHAR;
8879
8880         switch (gameMode) {
8881           case MachinePlaysBlack:
8882           case IcsPlayingBlack:
8883             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8884             break;
8885           case MachinePlaysWhite:
8886           case IcsPlayingWhite:
8887             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8888             break;
8889           case AnalyzeMode:
8890           case AnalyzeFile:
8891             break;
8892           case IcsObserving: /* [DM] icsEngineAnalyze */
8893             if (!appData.icsEngineAnalyze) ignore = TRUE;
8894             break;
8895           case TwoMachinesPlay:
8896             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8897                 ignore = TRUE;
8898             }
8899             break;
8900           default:
8901             ignore = TRUE;
8902             break;
8903         }
8904
8905         if (!ignore) {
8906             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8907             buf1[0] = NULLCHAR;
8908             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8909                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8910
8911                 if (plyext != ' ' && plyext != '\t') {
8912                     time *= 100;
8913                 }
8914
8915                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8916                 if( cps->scoreIsAbsolute &&
8917                     ( gameMode == MachinePlaysBlack ||
8918                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8919                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8920                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8921                      !WhiteOnMove(currentMove)
8922                     ) )
8923                 {
8924                     curscore = -curscore;
8925                 }
8926
8927                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8928
8929                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8930                         char buf[MSG_SIZ];
8931                         FILE *f;
8932                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8933                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8934                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8935                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8936                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8937                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8938                                 fclose(f);
8939                         } else DisplayError(_("failed writing PV"), 0);
8940                 }
8941
8942                 tempStats.depth = plylev;
8943                 tempStats.nodes = nodes;
8944                 tempStats.time = time;
8945                 tempStats.score = curscore;
8946                 tempStats.got_only_move = 0;
8947
8948                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8949                         int ticklen;
8950
8951                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8952                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8953                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8954                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8955                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8956                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8957                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8958                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8959                 }
8960
8961                 /* Buffer overflow protection */
8962                 if (pv[0] != NULLCHAR) {
8963                     if (strlen(pv) >= sizeof(tempStats.movelist)
8964                         && appData.debugMode) {
8965                         fprintf(debugFP,
8966                                 "PV is too long; using the first %u bytes.\n",
8967                                 (unsigned) sizeof(tempStats.movelist) - 1);
8968                     }
8969
8970                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8971                 } else {
8972                     sprintf(tempStats.movelist, " no PV\n");
8973                 }
8974
8975                 if (tempStats.seen_stat) {
8976                     tempStats.ok_to_send = 1;
8977                 }
8978
8979                 if (strchr(tempStats.movelist, '(') != NULL) {
8980                     tempStats.line_is_book = 1;
8981                     tempStats.nr_moves = 0;
8982                     tempStats.moves_left = 0;
8983                 } else {
8984                     tempStats.line_is_book = 0;
8985                 }
8986
8987                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8988                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8989
8990                 SendProgramStatsToFrontend( cps, &tempStats );
8991
8992                 /*
8993                     [AS] Protect the thinkOutput buffer from overflow... this
8994                     is only useful if buf1 hasn't overflowed first!
8995                 */
8996                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8997                          plylev,
8998                          (gameMode == TwoMachinesPlay ?
8999                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9000                          ((double) curscore) / 100.0,
9001                          prefixHint ? lastHint : "",
9002                          prefixHint ? " " : "" );
9003
9004                 if( buf1[0] != NULLCHAR ) {
9005                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9006
9007                     if( strlen(pv) > max_len ) {
9008                         if( appData.debugMode) {
9009                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9010                         }
9011                         pv[max_len+1] = '\0';
9012                     }
9013
9014                     strcat( thinkOutput, pv);
9015                 }
9016
9017                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9018                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9019                     DisplayMove(currentMove - 1);
9020                 }
9021                 return;
9022
9023             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9024                 /* crafty (9.25+) says "(only move) <move>"
9025                  * if there is only 1 legal move
9026                  */
9027                 sscanf(p, "(only move) %s", buf1);
9028                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9029                 sprintf(programStats.movelist, "%s (only move)", buf1);
9030                 programStats.depth = 1;
9031                 programStats.nr_moves = 1;
9032                 programStats.moves_left = 1;
9033                 programStats.nodes = 1;
9034                 programStats.time = 1;
9035                 programStats.got_only_move = 1;
9036
9037                 /* Not really, but we also use this member to
9038                    mean "line isn't going to change" (Crafty
9039                    isn't searching, so stats won't change) */
9040                 programStats.line_is_book = 1;
9041
9042                 SendProgramStatsToFrontend( cps, &programStats );
9043
9044                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9045                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9046                     DisplayMove(currentMove - 1);
9047                 }
9048                 return;
9049             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9050                               &time, &nodes, &plylev, &mvleft,
9051                               &mvtot, mvname) >= 5) {
9052                 /* The stat01: line is from Crafty (9.29+) in response
9053                    to the "." command */
9054                 programStats.seen_stat = 1;
9055                 cps->maybeThinking = TRUE;
9056
9057                 if (programStats.got_only_move || !appData.periodicUpdates)
9058                   return;
9059
9060                 programStats.depth = plylev;
9061                 programStats.time = time;
9062                 programStats.nodes = nodes;
9063                 programStats.moves_left = mvleft;
9064                 programStats.nr_moves = mvtot;
9065                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9066                 programStats.ok_to_send = 1;
9067                 programStats.movelist[0] = '\0';
9068
9069                 SendProgramStatsToFrontend( cps, &programStats );
9070
9071                 return;
9072
9073             } else if (strncmp(message,"++",2) == 0) {
9074                 /* Crafty 9.29+ outputs this */
9075                 programStats.got_fail = 2;
9076                 return;
9077
9078             } else if (strncmp(message,"--",2) == 0) {
9079                 /* Crafty 9.29+ outputs this */
9080                 programStats.got_fail = 1;
9081                 return;
9082
9083             } else if (thinkOutput[0] != NULLCHAR &&
9084                        strncmp(message, "    ", 4) == 0) {
9085                 unsigned message_len;
9086
9087                 p = message;
9088                 while (*p && *p == ' ') p++;
9089
9090                 message_len = strlen( p );
9091
9092                 /* [AS] Avoid buffer overflow */
9093                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9094                     strcat(thinkOutput, " ");
9095                     strcat(thinkOutput, p);
9096                 }
9097
9098                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9099                     strcat(programStats.movelist, " ");
9100                     strcat(programStats.movelist, p);
9101                 }
9102
9103                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9104                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9105                     DisplayMove(currentMove - 1);
9106                 }
9107                 return;
9108             }
9109         }
9110         else {
9111             buf1[0] = NULLCHAR;
9112
9113             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9114                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9115             {
9116                 ChessProgramStats cpstats;
9117
9118                 if (plyext != ' ' && plyext != '\t') {
9119                     time *= 100;
9120                 }
9121
9122                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9123                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9124                     curscore = -curscore;
9125                 }
9126
9127                 cpstats.depth = plylev;
9128                 cpstats.nodes = nodes;
9129                 cpstats.time = time;
9130                 cpstats.score = curscore;
9131                 cpstats.got_only_move = 0;
9132                 cpstats.movelist[0] = '\0';
9133
9134                 if (buf1[0] != NULLCHAR) {
9135                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9136                 }
9137
9138                 cpstats.ok_to_send = 0;
9139                 cpstats.line_is_book = 0;
9140                 cpstats.nr_moves = 0;
9141                 cpstats.moves_left = 0;
9142
9143                 SendProgramStatsToFrontend( cps, &cpstats );
9144             }
9145         }
9146     }
9147 }
9148
9149
9150 /* Parse a game score from the character string "game", and
9151    record it as the history of the current game.  The game
9152    score is NOT assumed to start from the standard position.
9153    The display is not updated in any way.
9154    */
9155 void
9156 ParseGameHistory (char *game)
9157 {
9158     ChessMove moveType;
9159     int fromX, fromY, toX, toY, boardIndex;
9160     char promoChar;
9161     char *p, *q;
9162     char buf[MSG_SIZ];
9163
9164     if (appData.debugMode)
9165       fprintf(debugFP, "Parsing game history: %s\n", game);
9166
9167     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9168     gameInfo.site = StrSave(appData.icsHost);
9169     gameInfo.date = PGNDate();
9170     gameInfo.round = StrSave("-");
9171
9172     /* Parse out names of players */
9173     while (*game == ' ') game++;
9174     p = buf;
9175     while (*game != ' ') *p++ = *game++;
9176     *p = NULLCHAR;
9177     gameInfo.white = StrSave(buf);
9178     while (*game == ' ') game++;
9179     p = buf;
9180     while (*game != ' ' && *game != '\n') *p++ = *game++;
9181     *p = NULLCHAR;
9182     gameInfo.black = StrSave(buf);
9183
9184     /* Parse moves */
9185     boardIndex = blackPlaysFirst ? 1 : 0;
9186     yynewstr(game);
9187     for (;;) {
9188         yyboardindex = boardIndex;
9189         moveType = (ChessMove) Myylex();
9190         switch (moveType) {
9191           case IllegalMove:             /* maybe suicide chess, etc. */
9192   if (appData.debugMode) {
9193     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9194     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9195     setbuf(debugFP, NULL);
9196   }
9197           case WhitePromotion:
9198           case BlackPromotion:
9199           case WhiteNonPromotion:
9200           case BlackNonPromotion:
9201           case NormalMove:
9202           case WhiteCapturesEnPassant:
9203           case BlackCapturesEnPassant:
9204           case WhiteKingSideCastle:
9205           case WhiteQueenSideCastle:
9206           case BlackKingSideCastle:
9207           case BlackQueenSideCastle:
9208           case WhiteKingSideCastleWild:
9209           case WhiteQueenSideCastleWild:
9210           case BlackKingSideCastleWild:
9211           case BlackQueenSideCastleWild:
9212           /* PUSH Fabien */
9213           case WhiteHSideCastleFR:
9214           case WhiteASideCastleFR:
9215           case BlackHSideCastleFR:
9216           case BlackASideCastleFR:
9217           /* POP Fabien */
9218             fromX = currentMoveString[0] - AAA;
9219             fromY = currentMoveString[1] - ONE;
9220             toX = currentMoveString[2] - AAA;
9221             toY = currentMoveString[3] - ONE;
9222             promoChar = currentMoveString[4];
9223             break;
9224           case WhiteDrop:
9225           case BlackDrop:
9226             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9227             fromX = moveType == WhiteDrop ?
9228               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9229             (int) CharToPiece(ToLower(currentMoveString[0]));
9230             fromY = DROP_RANK;
9231             toX = currentMoveString[2] - AAA;
9232             toY = currentMoveString[3] - ONE;
9233             promoChar = NULLCHAR;
9234             break;
9235           case AmbiguousMove:
9236             /* bug? */
9237             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9238   if (appData.debugMode) {
9239     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9240     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9241     setbuf(debugFP, NULL);
9242   }
9243             DisplayError(buf, 0);
9244             return;
9245           case ImpossibleMove:
9246             /* bug? */
9247             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9248   if (appData.debugMode) {
9249     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9250     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9251     setbuf(debugFP, NULL);
9252   }
9253             DisplayError(buf, 0);
9254             return;
9255           case EndOfFile:
9256             if (boardIndex < backwardMostMove) {
9257                 /* Oops, gap.  How did that happen? */
9258                 DisplayError(_("Gap in move list"), 0);
9259                 return;
9260             }
9261             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9262             if (boardIndex > forwardMostMove) {
9263                 forwardMostMove = boardIndex;
9264             }
9265             return;
9266           case ElapsedTime:
9267             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9268                 strcat(parseList[boardIndex-1], " ");
9269                 strcat(parseList[boardIndex-1], yy_text);
9270             }
9271             continue;
9272           case Comment:
9273           case PGNTag:
9274           case NAG:
9275           default:
9276             /* ignore */
9277             continue;
9278           case WhiteWins:
9279           case BlackWins:
9280           case GameIsDrawn:
9281           case GameUnfinished:
9282             if (gameMode == IcsExamining) {
9283                 if (boardIndex < backwardMostMove) {
9284                     /* Oops, gap.  How did that happen? */
9285                     return;
9286                 }
9287                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9288                 return;
9289             }
9290             gameInfo.result = moveType;
9291             p = strchr(yy_text, '{');
9292             if (p == NULL) p = strchr(yy_text, '(');
9293             if (p == NULL) {
9294                 p = yy_text;
9295                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9296             } else {
9297                 q = strchr(p, *p == '{' ? '}' : ')');
9298                 if (q != NULL) *q = NULLCHAR;
9299                 p++;
9300             }
9301             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9302             gameInfo.resultDetails = StrSave(p);
9303             continue;
9304         }
9305         if (boardIndex >= forwardMostMove &&
9306             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9307             backwardMostMove = blackPlaysFirst ? 1 : 0;
9308             return;
9309         }
9310         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9311                                  fromY, fromX, toY, toX, promoChar,
9312                                  parseList[boardIndex]);
9313         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9314         /* currentMoveString is set as a side-effect of yylex */
9315         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9316         strcat(moveList[boardIndex], "\n");
9317         boardIndex++;
9318         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9319         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9320           case MT_NONE:
9321           case MT_STALEMATE:
9322           default:
9323             break;
9324           case MT_CHECK:
9325             if(gameInfo.variant != VariantShogi)
9326                 strcat(parseList[boardIndex - 1], "+");
9327             break;
9328           case MT_CHECKMATE:
9329           case MT_STAINMATE:
9330             strcat(parseList[boardIndex - 1], "#");
9331             break;
9332         }
9333     }
9334 }
9335
9336
9337 /* Apply a move to the given board  */
9338 void
9339 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9340 {
9341   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9342   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9343
9344     /* [HGM] compute & store e.p. status and castling rights for new position */
9345     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9346
9347       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9348       oldEP = (signed char)board[EP_STATUS];
9349       board[EP_STATUS] = EP_NONE;
9350
9351   if (fromY == DROP_RANK) {
9352         /* must be first */
9353         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9354             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9355             return;
9356         }
9357         piece = board[toY][toX] = (ChessSquare) fromX;
9358   } else {
9359       int i;
9360
9361       if( board[toY][toX] != EmptySquare )
9362            board[EP_STATUS] = EP_CAPTURE;
9363
9364       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9365            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9366                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9367       } else
9368       if( board[fromY][fromX] == WhitePawn ) {
9369            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9370                board[EP_STATUS] = EP_PAWN_MOVE;
9371            if( toY-fromY==2) {
9372                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9373                         gameInfo.variant != VariantBerolina || toX < fromX)
9374                       board[EP_STATUS] = toX | berolina;
9375                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9376                         gameInfo.variant != VariantBerolina || toX > fromX)
9377                       board[EP_STATUS] = toX;
9378            }
9379       } else
9380       if( board[fromY][fromX] == BlackPawn ) {
9381            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9382                board[EP_STATUS] = EP_PAWN_MOVE;
9383            if( toY-fromY== -2) {
9384                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9385                         gameInfo.variant != VariantBerolina || toX < fromX)
9386                       board[EP_STATUS] = toX | berolina;
9387                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9388                         gameInfo.variant != VariantBerolina || toX > fromX)
9389                       board[EP_STATUS] = toX;
9390            }
9391        }
9392
9393        for(i=0; i<nrCastlingRights; i++) {
9394            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9395               board[CASTLING][i] == toX   && castlingRank[i] == toY
9396              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9397        }
9398
9399        if(gameInfo.variant == VariantSChess) { // update virginity
9400            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9401            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9402            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9403            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9404        }
9405
9406      if (fromX == toX && fromY == toY) return;
9407
9408      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9409      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9410      if(gameInfo.variant == VariantKnightmate)
9411          king += (int) WhiteUnicorn - (int) WhiteKing;
9412
9413     /* Code added by Tord: */
9414     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9415     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9416         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9417       board[fromY][fromX] = EmptySquare;
9418       board[toY][toX] = EmptySquare;
9419       if((toX > fromX) != (piece == WhiteRook)) {
9420         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9421       } else {
9422         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9423       }
9424     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9425                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9426       board[fromY][fromX] = EmptySquare;
9427       board[toY][toX] = EmptySquare;
9428       if((toX > fromX) != (piece == BlackRook)) {
9429         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9430       } else {
9431         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9432       }
9433     /* End of code added by Tord */
9434
9435     } else if (board[fromY][fromX] == king
9436         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9437         && toY == fromY && toX > fromX+1) {
9438         board[fromY][fromX] = EmptySquare;
9439         board[toY][toX] = king;
9440         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9441         board[fromY][BOARD_RGHT-1] = EmptySquare;
9442     } else if (board[fromY][fromX] == king
9443         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9444                && toY == fromY && toX < fromX-1) {
9445         board[fromY][fromX] = EmptySquare;
9446         board[toY][toX] = king;
9447         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9448         board[fromY][BOARD_LEFT] = EmptySquare;
9449     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9450                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9451                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9452                ) {
9453         /* white pawn promotion */
9454         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9455         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse)
9456            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9457             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9458         board[fromY][fromX] = EmptySquare;
9459     } else if ((fromY >= BOARD_HEIGHT>>1)
9460                && (toX != fromX)
9461                && gameInfo.variant != VariantXiangqi
9462                && gameInfo.variant != VariantBerolina
9463                && (board[fromY][fromX] == WhitePawn)
9464                && (board[toY][toX] == EmptySquare)) {
9465         board[fromY][fromX] = EmptySquare;
9466         board[toY][toX] = WhitePawn;
9467         captured = board[toY - 1][toX];
9468         board[toY - 1][toX] = EmptySquare;
9469     } else if ((fromY == BOARD_HEIGHT-4)
9470                && (toX == fromX)
9471                && gameInfo.variant == VariantBerolina
9472                && (board[fromY][fromX] == WhitePawn)
9473                && (board[toY][toX] == EmptySquare)) {
9474         board[fromY][fromX] = EmptySquare;
9475         board[toY][toX] = WhitePawn;
9476         if(oldEP & EP_BEROLIN_A) {
9477                 captured = board[fromY][fromX-1];
9478                 board[fromY][fromX-1] = EmptySquare;
9479         }else{  captured = board[fromY][fromX+1];
9480                 board[fromY][fromX+1] = EmptySquare;
9481         }
9482     } else if (board[fromY][fromX] == king
9483         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9484                && toY == fromY && toX > fromX+1) {
9485         board[fromY][fromX] = EmptySquare;
9486         board[toY][toX] = king;
9487         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9488         board[fromY][BOARD_RGHT-1] = EmptySquare;
9489     } else if (board[fromY][fromX] == king
9490         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9491                && toY == fromY && toX < fromX-1) {
9492         board[fromY][fromX] = EmptySquare;
9493         board[toY][toX] = king;
9494         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9495         board[fromY][BOARD_LEFT] = EmptySquare;
9496     } else if (fromY == 7 && fromX == 3
9497                && board[fromY][fromX] == BlackKing
9498                && toY == 7 && toX == 5) {
9499         board[fromY][fromX] = EmptySquare;
9500         board[toY][toX] = BlackKing;
9501         board[fromY][7] = EmptySquare;
9502         board[toY][4] = BlackRook;
9503     } else if (fromY == 7 && fromX == 3
9504                && board[fromY][fromX] == BlackKing
9505                && toY == 7 && toX == 1) {
9506         board[fromY][fromX] = EmptySquare;
9507         board[toY][toX] = BlackKing;
9508         board[fromY][0] = EmptySquare;
9509         board[toY][2] = BlackRook;
9510     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9511                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9512                && toY < promoRank && promoChar
9513                ) {
9514         /* black pawn promotion */
9515         board[toY][toX] = CharToPiece(ToLower(promoChar));
9516         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse)
9517            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9518             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9519         board[fromY][fromX] = EmptySquare;
9520     } else if ((fromY < BOARD_HEIGHT>>1)
9521                && (toX != fromX)
9522                && gameInfo.variant != VariantXiangqi
9523                && gameInfo.variant != VariantBerolina
9524                && (board[fromY][fromX] == BlackPawn)
9525                && (board[toY][toX] == EmptySquare)) {
9526         board[fromY][fromX] = EmptySquare;
9527         board[toY][toX] = BlackPawn;
9528         captured = board[toY + 1][toX];
9529         board[toY + 1][toX] = EmptySquare;
9530     } else if ((fromY == 3)
9531                && (toX == fromX)
9532                && gameInfo.variant == VariantBerolina
9533                && (board[fromY][fromX] == BlackPawn)
9534                && (board[toY][toX] == EmptySquare)) {
9535         board[fromY][fromX] = EmptySquare;
9536         board[toY][toX] = BlackPawn;
9537         if(oldEP & EP_BEROLIN_A) {
9538                 captured = board[fromY][fromX-1];
9539                 board[fromY][fromX-1] = EmptySquare;
9540         }else{  captured = board[fromY][fromX+1];
9541                 board[fromY][fromX+1] = EmptySquare;
9542         }
9543     } else {
9544         board[toY][toX] = board[fromY][fromX];
9545         board[fromY][fromX] = EmptySquare;
9546     }
9547   }
9548
9549     if (gameInfo.holdingsWidth != 0) {
9550
9551       /* !!A lot more code needs to be written to support holdings  */
9552       /* [HGM] OK, so I have written it. Holdings are stored in the */
9553       /* penultimate board files, so they are automaticlly stored   */
9554       /* in the game history.                                       */
9555       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9556                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9557         /* Delete from holdings, by decreasing count */
9558         /* and erasing image if necessary            */
9559         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9560         if(p < (int) BlackPawn) { /* white drop */
9561              p -= (int)WhitePawn;
9562                  p = PieceToNumber((ChessSquare)p);
9563              if(p >= gameInfo.holdingsSize) p = 0;
9564              if(--board[p][BOARD_WIDTH-2] <= 0)
9565                   board[p][BOARD_WIDTH-1] = EmptySquare;
9566              if((int)board[p][BOARD_WIDTH-2] < 0)
9567                         board[p][BOARD_WIDTH-2] = 0;
9568         } else {                  /* black drop */
9569              p -= (int)BlackPawn;
9570                  p = PieceToNumber((ChessSquare)p);
9571              if(p >= gameInfo.holdingsSize) p = 0;
9572              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9573                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9574              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9575                         board[BOARD_HEIGHT-1-p][1] = 0;
9576         }
9577       }
9578       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9579           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9580         /* [HGM] holdings: Add to holdings, if holdings exist */
9581         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9582                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9583                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9584         }
9585         p = (int) captured;
9586         if (p >= (int) BlackPawn) {
9587           p -= (int)BlackPawn;
9588           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9589                   /* in Shogi restore piece to its original  first */
9590                   captured = (ChessSquare) (DEMOTED captured);
9591                   p = DEMOTED p;
9592           }
9593           p = PieceToNumber((ChessSquare)p);
9594           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9595           board[p][BOARD_WIDTH-2]++;
9596           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9597         } else {
9598           p -= (int)WhitePawn;
9599           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9600                   captured = (ChessSquare) (DEMOTED captured);
9601                   p = DEMOTED p;
9602           }
9603           p = PieceToNumber((ChessSquare)p);
9604           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9605           board[BOARD_HEIGHT-1-p][1]++;
9606           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9607         }
9608       }
9609     } else if (gameInfo.variant == VariantAtomic) {
9610       if (captured != EmptySquare) {
9611         int y, x;
9612         for (y = toY-1; y <= toY+1; y++) {
9613           for (x = toX-1; x <= toX+1; x++) {
9614             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9615                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9616               board[y][x] = EmptySquare;
9617             }
9618           }
9619         }
9620         board[toY][toX] = EmptySquare;
9621       }
9622     }
9623     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9624         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9625     } else
9626     if(promoChar == '+') {
9627         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9628         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9629     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9630         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9631         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9632            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9633         board[toY][toX] = newPiece;
9634     }
9635     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9636                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9637         // [HGM] superchess: take promotion piece out of holdings
9638         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9639         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9640             if(!--board[k][BOARD_WIDTH-2])
9641                 board[k][BOARD_WIDTH-1] = EmptySquare;
9642         } else {
9643             if(!--board[BOARD_HEIGHT-1-k][1])
9644                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9645         }
9646     }
9647
9648 }
9649
9650 /* Updates forwardMostMove */
9651 void
9652 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9653 {
9654 //    forwardMostMove++; // [HGM] bare: moved downstream
9655
9656     (void) CoordsToAlgebraic(boards[forwardMostMove],
9657                              PosFlags(forwardMostMove),
9658                              fromY, fromX, toY, toX, promoChar,
9659                              parseList[forwardMostMove]);
9660
9661     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9662         int timeLeft; static int lastLoadFlag=0; int king, piece;
9663         piece = boards[forwardMostMove][fromY][fromX];
9664         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9665         if(gameInfo.variant == VariantKnightmate)
9666             king += (int) WhiteUnicorn - (int) WhiteKing;
9667         if(forwardMostMove == 0) {
9668             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9669                 fprintf(serverMoves, "%s;", UserName());
9670             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9671                 fprintf(serverMoves, "%s;", second.tidy);
9672             fprintf(serverMoves, "%s;", first.tidy);
9673             if(gameMode == MachinePlaysWhite)
9674                 fprintf(serverMoves, "%s;", UserName());
9675             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9676                 fprintf(serverMoves, "%s;", second.tidy);
9677         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9678         lastLoadFlag = loadFlag;
9679         // print base move
9680         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9681         // print castling suffix
9682         if( toY == fromY && piece == king ) {
9683             if(toX-fromX > 1)
9684                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9685             if(fromX-toX >1)
9686                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9687         }
9688         // e.p. suffix
9689         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9690              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9691              boards[forwardMostMove][toY][toX] == EmptySquare
9692              && fromX != toX && fromY != toY)
9693                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9694         // promotion suffix
9695         if(promoChar != NULLCHAR) {
9696             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9697                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9698                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9699             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9700         }
9701         if(!loadFlag) {
9702                 char buf[MOVE_LEN*2], *p; int len;
9703             fprintf(serverMoves, "/%d/%d",
9704                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9705             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9706             else                      timeLeft = blackTimeRemaining/1000;
9707             fprintf(serverMoves, "/%d", timeLeft);
9708                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9709                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9710                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9711                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9712             fprintf(serverMoves, "/%s", buf);
9713         }
9714         fflush(serverMoves);
9715     }
9716
9717     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9718         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9719       return;
9720     }
9721     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9722     if (commentList[forwardMostMove+1] != NULL) {
9723         free(commentList[forwardMostMove+1]);
9724         commentList[forwardMostMove+1] = NULL;
9725     }
9726     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9727     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9728     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9729     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9730     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9731     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9732     adjustedClock = FALSE;
9733     gameInfo.result = GameUnfinished;
9734     if (gameInfo.resultDetails != NULL) {
9735         free(gameInfo.resultDetails);
9736         gameInfo.resultDetails = NULL;
9737     }
9738     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9739                               moveList[forwardMostMove - 1]);
9740     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9741       case MT_NONE:
9742       case MT_STALEMATE:
9743       default:
9744         break;
9745       case MT_CHECK:
9746         if(gameInfo.variant != VariantShogi)
9747             strcat(parseList[forwardMostMove - 1], "+");
9748         break;
9749       case MT_CHECKMATE:
9750       case MT_STAINMATE:
9751         strcat(parseList[forwardMostMove - 1], "#");
9752         break;
9753     }
9754
9755 }
9756
9757 /* Updates currentMove if not pausing */
9758 void
9759 ShowMove (int fromX, int fromY, int toX, int toY)
9760 {
9761     int instant = (gameMode == PlayFromGameFile) ?
9762         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9763     if(appData.noGUI) return;
9764     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9765         if (!instant) {
9766             if (forwardMostMove == currentMove + 1) {
9767                 AnimateMove(boards[forwardMostMove - 1],
9768                             fromX, fromY, toX, toY);
9769             }
9770         }
9771         currentMove = forwardMostMove;
9772     }
9773
9774     if (instant) return;
9775
9776     DisplayMove(currentMove - 1);
9777     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9778             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9779                 SetHighlights(fromX, fromY, toX, toY);
9780             }
9781     }
9782     DrawPosition(FALSE, boards[currentMove]);
9783     DisplayBothClocks();
9784     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9785 }
9786
9787 void
9788 SendEgtPath (ChessProgramState *cps)
9789 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9790         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9791
9792         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9793
9794         while(*p) {
9795             char c, *q = name+1, *r, *s;
9796
9797             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9798             while(*p && *p != ',') *q++ = *p++;
9799             *q++ = ':'; *q = 0;
9800             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9801                 strcmp(name, ",nalimov:") == 0 ) {
9802                 // take nalimov path from the menu-changeable option first, if it is defined
9803               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9804                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9805             } else
9806             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9807                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9808                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9809                 s = r = StrStr(s, ":") + 1; // beginning of path info
9810                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9811                 c = *r; *r = 0;             // temporarily null-terminate path info
9812                     *--q = 0;               // strip of trailig ':' from name
9813                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9814                 *r = c;
9815                 SendToProgram(buf,cps);     // send egtbpath command for this format
9816             }
9817             if(*p == ',') p++; // read away comma to position for next format name
9818         }
9819 }
9820
9821 void
9822 InitChessProgram (ChessProgramState *cps, int setup)
9823 /* setup needed to setup FRC opening position */
9824 {
9825     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9826     if (appData.noChessProgram) return;
9827     hintRequested = FALSE;
9828     bookRequested = FALSE;
9829
9830     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9831     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9832     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9833     if(cps->memSize) { /* [HGM] memory */
9834       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9835         SendToProgram(buf, cps);
9836     }
9837     SendEgtPath(cps); /* [HGM] EGT */
9838     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9839       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9840         SendToProgram(buf, cps);
9841     }
9842
9843     SendToProgram(cps->initString, cps);
9844     if (gameInfo.variant != VariantNormal &&
9845         gameInfo.variant != VariantLoadable
9846         /* [HGM] also send variant if board size non-standard */
9847         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9848                                             ) {
9849       char *v = VariantName(gameInfo.variant);
9850       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9851         /* [HGM] in protocol 1 we have to assume all variants valid */
9852         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9853         DisplayFatalError(buf, 0, 1);
9854         return;
9855       }
9856
9857       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9858       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9859       if( gameInfo.variant == VariantXiangqi )
9860            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9861       if( gameInfo.variant == VariantShogi )
9862            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9863       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9864            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9865       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9866           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9867            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9868       if( gameInfo.variant == VariantCourier )
9869            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9870       if( gameInfo.variant == VariantSuper )
9871            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9872       if( gameInfo.variant == VariantGreat )
9873            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9874       if( gameInfo.variant == VariantSChess )
9875            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9876       if( gameInfo.variant == VariantGrand )
9877            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9878
9879       if(overruled) {
9880         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9881                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9882            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9883            if(StrStr(cps->variants, b) == NULL) {
9884                // specific sized variant not known, check if general sizing allowed
9885                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9886                    if(StrStr(cps->variants, "boardsize") == NULL) {
9887                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9888                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9889                        DisplayFatalError(buf, 0, 1);
9890                        return;
9891                    }
9892                    /* [HGM] here we really should compare with the maximum supported board size */
9893                }
9894            }
9895       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9896       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9897       SendToProgram(buf, cps);
9898     }
9899     currentlyInitializedVariant = gameInfo.variant;
9900
9901     /* [HGM] send opening position in FRC to first engine */
9902     if(setup) {
9903           SendToProgram("force\n", cps);
9904           SendBoard(cps, 0);
9905           /* engine is now in force mode! Set flag to wake it up after first move. */
9906           setboardSpoiledMachineBlack = 1;
9907     }
9908
9909     if (cps->sendICS) {
9910       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9911       SendToProgram(buf, cps);
9912     }
9913     cps->maybeThinking = FALSE;
9914     cps->offeredDraw = 0;
9915     if (!appData.icsActive) {
9916         SendTimeControl(cps, movesPerSession, timeControl,
9917                         timeIncrement, appData.searchDepth,
9918                         searchTime);
9919     }
9920     if (appData.showThinking
9921         // [HGM] thinking: four options require thinking output to be sent
9922         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9923                                 ) {
9924         SendToProgram("post\n", cps);
9925     }
9926     SendToProgram("hard\n", cps);
9927     if (!appData.ponderNextMove) {
9928         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9929            it without being sure what state we are in first.  "hard"
9930            is not a toggle, so that one is OK.
9931          */
9932         SendToProgram("easy\n", cps);
9933     }
9934     if (cps->usePing) {
9935       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9936       SendToProgram(buf, cps);
9937     }
9938     cps->initDone = TRUE;
9939     ClearEngineOutputPane(cps == &second);
9940 }
9941
9942
9943 void
9944 StartChessProgram (ChessProgramState *cps)
9945 {
9946     char buf[MSG_SIZ];
9947     int err;
9948
9949     if (appData.noChessProgram) return;
9950     cps->initDone = FALSE;
9951
9952     if (strcmp(cps->host, "localhost") == 0) {
9953         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9954     } else if (*appData.remoteShell == NULLCHAR) {
9955         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9956     } else {
9957         if (*appData.remoteUser == NULLCHAR) {
9958           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9959                     cps->program);
9960         } else {
9961           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9962                     cps->host, appData.remoteUser, cps->program);
9963         }
9964         err = StartChildProcess(buf, "", &cps->pr);
9965     }
9966
9967     if (err != 0) {
9968       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9969         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9970         if(cps != &first) return;
9971         appData.noChessProgram = TRUE;
9972         ThawUI();
9973         SetNCPMode();
9974 //      DisplayFatalError(buf, err, 1);
9975 //      cps->pr = NoProc;
9976 //      cps->isr = NULL;
9977         return;
9978     }
9979
9980     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9981     if (cps->protocolVersion > 1) {
9982       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9983       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9984       cps->comboCnt = 0;  //                and values of combo boxes
9985       SendToProgram(buf, cps);
9986     } else {
9987       SendToProgram("xboard\n", cps);
9988     }
9989 }
9990
9991 void
9992 TwoMachinesEventIfReady P((void))
9993 {
9994   static int curMess = 0;
9995   if (first.lastPing != first.lastPong) {
9996     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9997     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9998     return;
9999   }
10000   if (second.lastPing != second.lastPong) {
10001     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10002     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10003     return;
10004   }
10005   DisplayMessage("", ""); curMess = 0;
10006   ThawUI();
10007   TwoMachinesEvent();
10008 }
10009
10010 char *
10011 MakeName (char *template)
10012 {
10013     time_t clock;
10014     struct tm *tm;
10015     static char buf[MSG_SIZ];
10016     char *p = buf;
10017     int i;
10018
10019     clock = time((time_t *)NULL);
10020     tm = localtime(&clock);
10021
10022     while(*p++ = *template++) if(p[-1] == '%') {
10023         switch(*template++) {
10024           case 0:   *p = 0; return buf;
10025           case 'Y': i = tm->tm_year+1900; break;
10026           case 'y': i = tm->tm_year-100; break;
10027           case 'M': i = tm->tm_mon+1; break;
10028           case 'd': i = tm->tm_mday; break;
10029           case 'h': i = tm->tm_hour; break;
10030           case 'm': i = tm->tm_min; break;
10031           case 's': i = tm->tm_sec; break;
10032           default:  i = 0;
10033         }
10034         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10035     }
10036     return buf;
10037 }
10038
10039 int
10040 CountPlayers (char *p)
10041 {
10042     int n = 0;
10043     while(p = strchr(p, '\n')) p++, n++; // count participants
10044     return n;
10045 }
10046
10047 FILE *
10048 WriteTourneyFile (char *results, FILE *f)
10049 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10050     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10051     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10052         // create a file with tournament description
10053         fprintf(f, "-participants {%s}\n", appData.participants);
10054         fprintf(f, "-seedBase %d\n", appData.seedBase);
10055         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10056         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10057         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10058         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10059         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10060         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10061         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10062         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10063         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10064         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10065         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10066         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10067         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10068         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10069         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10070         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10071         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10072         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10073         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10074         fprintf(f, "-smpCores %d\n", appData.smpCores);
10075         if(searchTime > 0)
10076                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10077         else {
10078                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10079                 fprintf(f, "-tc %s\n", appData.timeControl);
10080                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10081         }
10082         fprintf(f, "-results \"%s\"\n", results);
10083     }
10084     return f;
10085 }
10086
10087 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10088
10089 void
10090 Substitute (char *participants, int expunge)
10091 {
10092     int i, changed, changes=0, nPlayers=0;
10093     char *p, *q, *r, buf[MSG_SIZ];
10094     if(participants == NULL) return;
10095     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10096     r = p = participants; q = appData.participants;
10097     while(*p && *p == *q) {
10098         if(*p == '\n') r = p+1, nPlayers++;
10099         p++; q++;
10100     }
10101     if(*p) { // difference
10102         while(*p && *p++ != '\n');
10103         while(*q && *q++ != '\n');
10104       changed = nPlayers;
10105         changes = 1 + (strcmp(p, q) != 0);
10106     }
10107     if(changes == 1) { // a single engine mnemonic was changed
10108         q = r; while(*q) nPlayers += (*q++ == '\n');
10109         p = buf; while(*r && (*p = *r++) != '\n') p++;
10110         *p = NULLCHAR;
10111         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10112         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10113         if(mnemonic[i]) { // The substitute is valid
10114             FILE *f;
10115             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10116                 flock(fileno(f), LOCK_EX);
10117                 ParseArgsFromFile(f);
10118                 fseek(f, 0, SEEK_SET);
10119                 FREE(appData.participants); appData.participants = participants;
10120                 if(expunge) { // erase results of replaced engine
10121                     int len = strlen(appData.results), w, b, dummy;
10122                     for(i=0; i<len; i++) {
10123                         Pairing(i, nPlayers, &w, &b, &dummy);
10124                         if((w == changed || b == changed) && appData.results[i] == '*') {
10125                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10126                             fclose(f);
10127                             return;
10128                         }
10129                     }
10130                     for(i=0; i<len; i++) {
10131                         Pairing(i, nPlayers, &w, &b, &dummy);
10132                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10133                     }
10134                 }
10135                 WriteTourneyFile(appData.results, f);
10136                 fclose(f); // release lock
10137                 return;
10138             }
10139         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10140     }
10141     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10142     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10143     free(participants);
10144     return;
10145 }
10146
10147 int
10148 CheckPlayers (char *participants)
10149 {
10150         int i;
10151         char buf[MSG_SIZ], *p;
10152         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10153         while(p = strchr(participants, '\n')) {
10154             *p = NULLCHAR;
10155             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10156             if(!mnemonic[i]) {
10157                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10158                 *p = '\n';
10159                 DisplayError(buf, 0);
10160                 return 1;
10161             }
10162             *p = '\n';
10163             participants = p + 1;
10164         }
10165         return 0;
10166 }
10167
10168 int
10169 CreateTourney (char *name)
10170 {
10171         FILE *f;
10172         if(matchMode && strcmp(name, appData.tourneyFile)) {
10173              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10174         }
10175         if(name[0] == NULLCHAR) {
10176             if(appData.participants[0])
10177                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10178             return 0;
10179         }
10180         f = fopen(name, "r");
10181         if(f) { // file exists
10182             ASSIGN(appData.tourneyFile, name);
10183             ParseArgsFromFile(f); // parse it
10184         } else {
10185             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10186             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10187                 DisplayError(_("Not enough participants"), 0);
10188                 return 0;
10189             }
10190             if(CheckPlayers(appData.participants)) return 0;
10191             ASSIGN(appData.tourneyFile, name);
10192             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10193             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10194         }
10195         fclose(f);
10196         appData.noChessProgram = FALSE;
10197         appData.clockMode = TRUE;
10198         SetGNUMode();
10199         return 1;
10200 }
10201
10202 int
10203 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10204 {
10205     char buf[MSG_SIZ], *p, *q;
10206     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10207     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10208     skip = !all && group[0]; // if group requested, we start in skip mode
10209     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10210         p = names; q = buf; header = 0;
10211         while(*p && *p != '\n') *q++ = *p++;
10212         *q = 0;
10213         if(*p == '\n') p++;
10214         if(buf[0] == '#') {
10215             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10216             depth++; // we must be entering a new group
10217             if(all) continue; // suppress printing group headers when complete list requested
10218             header = 1;
10219             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10220         }
10221         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10222         if(engineList[i]) free(engineList[i]);
10223         engineList[i] = strdup(buf);
10224         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10225         if(engineMnemonic[i]) free(engineMnemonic[i]);
10226         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10227             strcat(buf, " (");
10228             sscanf(q + 8, "%s", buf + strlen(buf));
10229             strcat(buf, ")");
10230         }
10231         engineMnemonic[i] = strdup(buf);
10232         i++;
10233     }
10234     engineList[i] = engineMnemonic[i] = NULL;
10235     return i;
10236 }
10237
10238 // following implemented as macro to avoid type limitations
10239 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10240
10241 void
10242 SwapEngines (int n)
10243 {   // swap settings for first engine and other engine (so far only some selected options)
10244     int h;
10245     char *p;
10246     if(n == 0) return;
10247     SWAP(directory, p)
10248     SWAP(chessProgram, p)
10249     SWAP(isUCI, h)
10250     SWAP(hasOwnBookUCI, h)
10251     SWAP(protocolVersion, h)
10252     SWAP(reuse, h)
10253     SWAP(scoreIsAbsolute, h)
10254     SWAP(timeOdds, h)
10255     SWAP(logo, p)
10256     SWAP(pgnName, p)
10257     SWAP(pvSAN, h)
10258     SWAP(engOptions, p)
10259     SWAP(engInitString, p)
10260     SWAP(computerString, p)
10261     SWAP(features, p)
10262     SWAP(fenOverride, p)
10263     SWAP(NPS, h)
10264     SWAP(accumulateTC, h)
10265     SWAP(host, p)
10266 }
10267
10268 int
10269 GetEngineLine (char *s, int n)
10270 {
10271     int i;
10272     char buf[MSG_SIZ];
10273     extern char *icsNames;
10274     if(!s || !*s) return 0;
10275     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10276     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10277     if(!mnemonic[i]) return 0;
10278     if(n == 11) return 1; // just testing if there was a match
10279     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10280     if(n == 1) SwapEngines(n);
10281     ParseArgsFromString(buf);
10282     if(n == 1) SwapEngines(n);
10283     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10284         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10285         ParseArgsFromString(buf);
10286     }
10287     return 1;
10288 }
10289
10290 int
10291 SetPlayer (int player, char *p)
10292 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10293     int i;
10294     char buf[MSG_SIZ], *engineName;
10295     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10296     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10297     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10298     if(mnemonic[i]) {
10299         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10300         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10301         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10302         ParseArgsFromString(buf);
10303     }
10304     free(engineName);
10305     return i;
10306 }
10307
10308 char *recentEngines;
10309
10310 void
10311 RecentEngineEvent (int nr)
10312 {
10313     int n;
10314 //    SwapEngines(1); // bump first to second
10315 //    ReplaceEngine(&second, 1); // and load it there
10316     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10317     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10318     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10319         ReplaceEngine(&first, 0);
10320         FloatToFront(&appData.recentEngineList, command[n]);
10321     }
10322 }
10323
10324 int
10325 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10326 {   // determine players from game number
10327     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10328
10329     if(appData.tourneyType == 0) {
10330         roundsPerCycle = (nPlayers - 1) | 1;
10331         pairingsPerRound = nPlayers / 2;
10332     } else if(appData.tourneyType > 0) {
10333         roundsPerCycle = nPlayers - appData.tourneyType;
10334         pairingsPerRound = appData.tourneyType;
10335     }
10336     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10337     gamesPerCycle = gamesPerRound * roundsPerCycle;
10338     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10339     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10340     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10341     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10342     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10343     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10344
10345     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10346     if(appData.roundSync) *syncInterval = gamesPerRound;
10347
10348     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10349
10350     if(appData.tourneyType == 0) {
10351         if(curPairing == (nPlayers-1)/2 ) {
10352             *whitePlayer = curRound;
10353             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10354         } else {
10355             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10356             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10357             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10358             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10359         }
10360     } else if(appData.tourneyType > 1) {
10361         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10362         *whitePlayer = curRound + appData.tourneyType;
10363     } else if(appData.tourneyType > 0) {
10364         *whitePlayer = curPairing;
10365         *blackPlayer = curRound + appData.tourneyType;
10366     }
10367
10368     // take care of white/black alternation per round.
10369     // For cycles and games this is already taken care of by default, derived from matchGame!
10370     return curRound & 1;
10371 }
10372
10373 int
10374 NextTourneyGame (int nr, int *swapColors)
10375 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10376     char *p, *q;
10377     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10378     FILE *tf;
10379     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10380     tf = fopen(appData.tourneyFile, "r");
10381     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10382     ParseArgsFromFile(tf); fclose(tf);
10383     InitTimeControls(); // TC might be altered from tourney file
10384
10385     nPlayers = CountPlayers(appData.participants); // count participants
10386     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10387     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10388
10389     if(syncInterval) {
10390         p = q = appData.results;
10391         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10392         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10393             DisplayMessage(_("Waiting for other game(s)"),"");
10394             waitingForGame = TRUE;
10395             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10396             return 0;
10397         }
10398         waitingForGame = FALSE;
10399     }
10400
10401     if(appData.tourneyType < 0) {
10402         if(nr>=0 && !pairingReceived) {
10403             char buf[1<<16];
10404             if(pairing.pr == NoProc) {
10405                 if(!appData.pairingEngine[0]) {
10406                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10407                     return 0;
10408                 }
10409                 StartChessProgram(&pairing); // starts the pairing engine
10410             }
10411             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10412             SendToProgram(buf, &pairing);
10413             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10414             SendToProgram(buf, &pairing);
10415             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10416         }
10417         pairingReceived = 0;                              // ... so we continue here
10418         *swapColors = 0;
10419         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10420         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10421         matchGame = 1; roundNr = nr / syncInterval + 1;
10422     }
10423
10424     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10425
10426     // redefine engines, engine dir, etc.
10427     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10428     if(first.pr == NoProc) {
10429       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10430       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10431     }
10432     if(second.pr == NoProc) {
10433       SwapEngines(1);
10434       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10435       SwapEngines(1);         // and make that valid for second engine by swapping
10436       InitEngine(&second, 1);
10437     }
10438     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10439     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10440     return 1;
10441 }
10442
10443 void
10444 NextMatchGame ()
10445 {   // performs game initialization that does not invoke engines, and then tries to start the game
10446     int res, firstWhite, swapColors = 0;
10447     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10448     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
10449         char buf[MSG_SIZ];
10450         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10451         if(strcmp(buf, currentDebugFile)) { // name has changed
10452             FILE *f = fopen(buf, "w");
10453             if(f) { // if opening the new file failed, just keep using the old one
10454                 ASSIGN(currentDebugFile, buf);
10455                 fclose(debugFP);
10456                 debugFP = f;
10457             }
10458             if(appData.serverFileName) {
10459                 if(serverFP) fclose(serverFP);
10460                 serverFP = fopen(appData.serverFileName, "w");
10461                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10462                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10463             }
10464         }
10465     }
10466     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10467     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10468     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10469     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10470     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10471     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10472     Reset(FALSE, first.pr != NoProc);
10473     res = LoadGameOrPosition(matchGame); // setup game
10474     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10475     if(!res) return; // abort when bad game/pos file
10476     TwoMachinesEvent();
10477 }
10478
10479 void
10480 UserAdjudicationEvent (int result)
10481 {
10482     ChessMove gameResult = GameIsDrawn;
10483
10484     if( result > 0 ) {
10485         gameResult = WhiteWins;
10486     }
10487     else if( result < 0 ) {
10488         gameResult = BlackWins;
10489     }
10490
10491     if( gameMode == TwoMachinesPlay ) {
10492         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10493     }
10494 }
10495
10496
10497 // [HGM] save: calculate checksum of game to make games easily identifiable
10498 int
10499 StringCheckSum (char *s)
10500 {
10501         int i = 0;
10502         if(s==NULL) return 0;
10503         while(*s) i = i*259 + *s++;
10504         return i;
10505 }
10506
10507 int
10508 GameCheckSum ()
10509 {
10510         int i, sum=0;
10511         for(i=backwardMostMove; i<forwardMostMove; i++) {
10512                 sum += pvInfoList[i].depth;
10513                 sum += StringCheckSum(parseList[i]);
10514                 sum += StringCheckSum(commentList[i]);
10515                 sum *= 261;
10516         }
10517         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10518         return sum + StringCheckSum(commentList[i]);
10519 } // end of save patch
10520
10521 void
10522 GameEnds (ChessMove result, char *resultDetails, int whosays)
10523 {
10524     GameMode nextGameMode;
10525     int isIcsGame;
10526     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10527
10528     if(endingGame) return; /* [HGM] crash: forbid recursion */
10529     endingGame = 1;
10530     if(twoBoards) { // [HGM] dual: switch back to one board
10531         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10532         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10533     }
10534     if (appData.debugMode) {
10535       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10536               result, resultDetails ? resultDetails : "(null)", whosays);
10537     }
10538
10539     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10540
10541     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10542
10543     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10544         /* If we are playing on ICS, the server decides when the
10545            game is over, but the engine can offer to draw, claim
10546            a draw, or resign.
10547          */
10548 #if ZIPPY
10549         if (appData.zippyPlay && first.initDone) {
10550             if (result == GameIsDrawn) {
10551                 /* In case draw still needs to be claimed */
10552                 SendToICS(ics_prefix);
10553                 SendToICS("draw\n");
10554             } else if (StrCaseStr(resultDetails, "resign")) {
10555                 SendToICS(ics_prefix);
10556                 SendToICS("resign\n");
10557             }
10558         }
10559 #endif
10560         endingGame = 0; /* [HGM] crash */
10561         return;
10562     }
10563
10564     /* If we're loading the game from a file, stop */
10565     if (whosays == GE_FILE) {
10566       (void) StopLoadGameTimer();
10567       gameFileFP = NULL;
10568     }
10569
10570     /* Cancel draw offers */
10571     first.offeredDraw = second.offeredDraw = 0;
10572
10573     /* If this is an ICS game, only ICS can really say it's done;
10574        if not, anyone can. */
10575     isIcsGame = (gameMode == IcsPlayingWhite ||
10576                  gameMode == IcsPlayingBlack ||
10577                  gameMode == IcsObserving    ||
10578                  gameMode == IcsExamining);
10579
10580     if (!isIcsGame || whosays == GE_ICS) {
10581         /* OK -- not an ICS game, or ICS said it was done */
10582         StopClocks();
10583         if (!isIcsGame && !appData.noChessProgram)
10584           SetUserThinkingEnables();
10585
10586         /* [HGM] if a machine claims the game end we verify this claim */
10587         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10588             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10589                 char claimer;
10590                 ChessMove trueResult = (ChessMove) -1;
10591
10592                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10593                                             first.twoMachinesColor[0] :
10594                                             second.twoMachinesColor[0] ;
10595
10596                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10597                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10598                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10599                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10600                 } else
10601                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10602                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10603                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10604                 } else
10605                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10606                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10607                 }
10608
10609                 // now verify win claims, but not in drop games, as we don't understand those yet
10610                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10611                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10612                     (result == WhiteWins && claimer == 'w' ||
10613                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10614                       if (appData.debugMode) {
10615                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10616                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10617                       }
10618                       if(result != trueResult) {
10619                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10620                               result = claimer == 'w' ? BlackWins : WhiteWins;
10621                               resultDetails = buf;
10622                       }
10623                 } else
10624                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10625                     && (forwardMostMove <= backwardMostMove ||
10626                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10627                         (claimer=='b')==(forwardMostMove&1))
10628                                                                                   ) {
10629                       /* [HGM] verify: draws that were not flagged are false claims */
10630                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10631                       result = claimer == 'w' ? BlackWins : WhiteWins;
10632                       resultDetails = buf;
10633                 }
10634                 /* (Claiming a loss is accepted no questions asked!) */
10635             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10636                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10637                 result = GameUnfinished;
10638                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10639             }
10640             /* [HGM] bare: don't allow bare King to win */
10641             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10642                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10643                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10644                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10645                && result != GameIsDrawn)
10646             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10647                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10648                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10649                         if(p >= 0 && p <= (int)WhiteKing) k++;
10650                 }
10651                 if (appData.debugMode) {
10652                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10653                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10654                 }
10655                 if(k <= 1) {
10656                         result = GameIsDrawn;
10657                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10658                         resultDetails = buf;
10659                 }
10660             }
10661         }
10662
10663
10664         if(serverMoves != NULL && !loadFlag) { char c = '=';
10665             if(result==WhiteWins) c = '+';
10666             if(result==BlackWins) c = '-';
10667             if(resultDetails != NULL)
10668                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10669         }
10670         if (resultDetails != NULL) {
10671             gameInfo.result = result;
10672             gameInfo.resultDetails = StrSave(resultDetails);
10673
10674             /* display last move only if game was not loaded from file */
10675             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10676                 DisplayMove(currentMove - 1);
10677
10678             if (forwardMostMove != 0) {
10679                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10680                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10681                                                                 ) {
10682                     if (*appData.saveGameFile != NULLCHAR) {
10683                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10684                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10685                         else
10686                         SaveGameToFile(appData.saveGameFile, TRUE);
10687                     } else if (appData.autoSaveGames) {
10688                         AutoSaveGame();
10689                     }
10690                     if (*appData.savePositionFile != NULLCHAR) {
10691                         SavePositionToFile(appData.savePositionFile);
10692                     }
10693                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10694                 }
10695             }
10696
10697             /* Tell program how game ended in case it is learning */
10698             /* [HGM] Moved this to after saving the PGN, just in case */
10699             /* engine died and we got here through time loss. In that */
10700             /* case we will get a fatal error writing the pipe, which */
10701             /* would otherwise lose us the PGN.                       */
10702             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10703             /* output during GameEnds should never be fatal anymore   */
10704             if (gameMode == MachinePlaysWhite ||
10705                 gameMode == MachinePlaysBlack ||
10706                 gameMode == TwoMachinesPlay ||
10707                 gameMode == IcsPlayingWhite ||
10708                 gameMode == IcsPlayingBlack ||
10709                 gameMode == BeginningOfGame) {
10710                 char buf[MSG_SIZ];
10711                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10712                         resultDetails);
10713                 if (first.pr != NoProc) {
10714                     SendToProgram(buf, &first);
10715                 }
10716                 if (second.pr != NoProc &&
10717                     gameMode == TwoMachinesPlay) {
10718                     SendToProgram(buf, &second);
10719                 }
10720             }
10721         }
10722
10723         if (appData.icsActive) {
10724             if (appData.quietPlay &&
10725                 (gameMode == IcsPlayingWhite ||
10726                  gameMode == IcsPlayingBlack)) {
10727                 SendToICS(ics_prefix);
10728                 SendToICS("set shout 1\n");
10729             }
10730             nextGameMode = IcsIdle;
10731             ics_user_moved = FALSE;
10732             /* clean up premove.  It's ugly when the game has ended and the
10733              * premove highlights are still on the board.
10734              */
10735             if (gotPremove) {
10736               gotPremove = FALSE;
10737               ClearPremoveHighlights();
10738               DrawPosition(FALSE, boards[currentMove]);
10739             }
10740             if (whosays == GE_ICS) {
10741                 switch (result) {
10742                 case WhiteWins:
10743                     if (gameMode == IcsPlayingWhite)
10744                         PlayIcsWinSound();
10745                     else if(gameMode == IcsPlayingBlack)
10746                         PlayIcsLossSound();
10747                     break;
10748                 case BlackWins:
10749                     if (gameMode == IcsPlayingBlack)
10750                         PlayIcsWinSound();
10751                     else if(gameMode == IcsPlayingWhite)
10752                         PlayIcsLossSound();
10753                     break;
10754                 case GameIsDrawn:
10755                     PlayIcsDrawSound();
10756                     break;
10757                 default:
10758                     PlayIcsUnfinishedSound();
10759                 }
10760             }
10761         } else if (gameMode == EditGame ||
10762                    gameMode == PlayFromGameFile ||
10763                    gameMode == AnalyzeMode ||
10764                    gameMode == AnalyzeFile) {
10765             nextGameMode = gameMode;
10766         } else {
10767             nextGameMode = EndOfGame;
10768         }
10769         pausing = FALSE;
10770         ModeHighlight();
10771     } else {
10772         nextGameMode = gameMode;
10773     }
10774
10775     if (appData.noChessProgram) {
10776         gameMode = nextGameMode;
10777         ModeHighlight();
10778         endingGame = 0; /* [HGM] crash */
10779         return;
10780     }
10781
10782     if (first.reuse) {
10783         /* Put first chess program into idle state */
10784         if (first.pr != NoProc &&
10785             (gameMode == MachinePlaysWhite ||
10786              gameMode == MachinePlaysBlack ||
10787              gameMode == TwoMachinesPlay ||
10788              gameMode == IcsPlayingWhite ||
10789              gameMode == IcsPlayingBlack ||
10790              gameMode == BeginningOfGame)) {
10791             SendToProgram("force\n", &first);
10792             if (first.usePing) {
10793               char buf[MSG_SIZ];
10794               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10795               SendToProgram(buf, &first);
10796             }
10797         }
10798     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10799         /* Kill off first chess program */
10800         if (first.isr != NULL)
10801           RemoveInputSource(first.isr);
10802         first.isr = NULL;
10803
10804         if (first.pr != NoProc) {
10805             ExitAnalyzeMode();
10806             DoSleep( appData.delayBeforeQuit );
10807             SendToProgram("quit\n", &first);
10808             DoSleep( appData.delayAfterQuit );
10809             DestroyChildProcess(first.pr, first.useSigterm);
10810         }
10811         first.pr = NoProc;
10812     }
10813     if (second.reuse) {
10814         /* Put second chess program into idle state */
10815         if (second.pr != NoProc &&
10816             gameMode == TwoMachinesPlay) {
10817             SendToProgram("force\n", &second);
10818             if (second.usePing) {
10819               char buf[MSG_SIZ];
10820               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10821               SendToProgram(buf, &second);
10822             }
10823         }
10824     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10825         /* Kill off second chess program */
10826         if (second.isr != NULL)
10827           RemoveInputSource(second.isr);
10828         second.isr = NULL;
10829
10830         if (second.pr != NoProc) {
10831             DoSleep( appData.delayBeforeQuit );
10832             SendToProgram("quit\n", &second);
10833             DoSleep( appData.delayAfterQuit );
10834             DestroyChildProcess(second.pr, second.useSigterm);
10835         }
10836         second.pr = NoProc;
10837     }
10838
10839     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10840         char resChar = '=';
10841         switch (result) {
10842         case WhiteWins:
10843           resChar = '+';
10844           if (first.twoMachinesColor[0] == 'w') {
10845             first.matchWins++;
10846           } else {
10847             second.matchWins++;
10848           }
10849           break;
10850         case BlackWins:
10851           resChar = '-';
10852           if (first.twoMachinesColor[0] == 'b') {
10853             first.matchWins++;
10854           } else {
10855             second.matchWins++;
10856           }
10857           break;
10858         case GameUnfinished:
10859           resChar = ' ';
10860         default:
10861           break;
10862         }
10863
10864         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10865         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10866             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10867             ReserveGame(nextGame, resChar); // sets nextGame
10868             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10869             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10870         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10871
10872         if (nextGame <= appData.matchGames && !abortMatch) {
10873             gameMode = nextGameMode;
10874             matchGame = nextGame; // this will be overruled in tourney mode!
10875             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10876             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10877             endingGame = 0; /* [HGM] crash */
10878             return;
10879         } else {
10880             gameMode = nextGameMode;
10881             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10882                      first.tidy, second.tidy,
10883                      first.matchWins, second.matchWins,
10884                      appData.matchGames - (first.matchWins + second.matchWins));
10885             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10886             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10887             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10888             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10889                 first.twoMachinesColor = "black\n";
10890                 second.twoMachinesColor = "white\n";
10891             } else {
10892                 first.twoMachinesColor = "white\n";
10893                 second.twoMachinesColor = "black\n";
10894             }
10895         }
10896     }
10897     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10898         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10899       ExitAnalyzeMode();
10900     gameMode = nextGameMode;
10901     ModeHighlight();
10902     endingGame = 0;  /* [HGM] crash */
10903     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10904         if(matchMode == TRUE) { // match through command line: exit with or without popup
10905             if(ranking) {
10906                 ToNrEvent(forwardMostMove);
10907                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10908                 else ExitEvent(0);
10909             } else DisplayFatalError(buf, 0, 0);
10910         } else { // match through menu; just stop, with or without popup
10911             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10912             ModeHighlight();
10913             if(ranking){
10914                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10915             } else DisplayNote(buf);
10916       }
10917       if(ranking) free(ranking);
10918     }
10919 }
10920
10921 /* Assumes program was just initialized (initString sent).
10922    Leaves program in force mode. */
10923 void
10924 FeedMovesToProgram (ChessProgramState *cps, int upto)
10925 {
10926     int i;
10927
10928     if (appData.debugMode)
10929       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10930               startedFromSetupPosition ? "position and " : "",
10931               backwardMostMove, upto, cps->which);
10932     if(currentlyInitializedVariant != gameInfo.variant) {
10933       char buf[MSG_SIZ];
10934         // [HGM] variantswitch: make engine aware of new variant
10935         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10936                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10937         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10938         SendToProgram(buf, cps);
10939         currentlyInitializedVariant = gameInfo.variant;
10940     }
10941     SendToProgram("force\n", cps);
10942     if (startedFromSetupPosition) {
10943         SendBoard(cps, backwardMostMove);
10944     if (appData.debugMode) {
10945         fprintf(debugFP, "feedMoves\n");
10946     }
10947     }
10948     for (i = backwardMostMove; i < upto; i++) {
10949         SendMoveToProgram(i, cps);
10950     }
10951 }
10952
10953
10954 int
10955 ResurrectChessProgram ()
10956 {
10957      /* The chess program may have exited.
10958         If so, restart it and feed it all the moves made so far. */
10959     static int doInit = 0;
10960
10961     if (appData.noChessProgram) return 1;
10962
10963     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10964         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10965         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10966         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10967     } else {
10968         if (first.pr != NoProc) return 1;
10969         StartChessProgram(&first);
10970     }
10971     InitChessProgram(&first, FALSE);
10972     FeedMovesToProgram(&first, currentMove);
10973
10974     if (!first.sendTime) {
10975         /* can't tell gnuchess what its clock should read,
10976            so we bow to its notion. */
10977         ResetClocks();
10978         timeRemaining[0][currentMove] = whiteTimeRemaining;
10979         timeRemaining[1][currentMove] = blackTimeRemaining;
10980     }
10981
10982     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10983                 appData.icsEngineAnalyze) && first.analysisSupport) {
10984       SendToProgram("analyze\n", &first);
10985       first.analyzing = TRUE;
10986     }
10987     return 1;
10988 }
10989
10990 /*
10991  * Button procedures
10992  */
10993 void
10994 Reset (int redraw, int init)
10995 {
10996     int i;
10997
10998     if (appData.debugMode) {
10999         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11000                 redraw, init, gameMode);
11001     }
11002     CleanupTail(); // [HGM] vari: delete any stored variations
11003     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11004     pausing = pauseExamInvalid = FALSE;
11005     startedFromSetupPosition = blackPlaysFirst = FALSE;
11006     firstMove = TRUE;
11007     whiteFlag = blackFlag = FALSE;
11008     userOfferedDraw = FALSE;
11009     hintRequested = bookRequested = FALSE;
11010     first.maybeThinking = FALSE;
11011     second.maybeThinking = FALSE;
11012     first.bookSuspend = FALSE; // [HGM] book
11013     second.bookSuspend = FALSE;
11014     thinkOutput[0] = NULLCHAR;
11015     lastHint[0] = NULLCHAR;
11016     ClearGameInfo(&gameInfo);
11017     gameInfo.variant = StringToVariant(appData.variant);
11018     ics_user_moved = ics_clock_paused = FALSE;
11019     ics_getting_history = H_FALSE;
11020     ics_gamenum = -1;
11021     white_holding[0] = black_holding[0] = NULLCHAR;
11022     ClearProgramStats();
11023     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11024
11025     ResetFrontEnd();
11026     ClearHighlights();
11027     flipView = appData.flipView;
11028     ClearPremoveHighlights();
11029     gotPremove = FALSE;
11030     alarmSounded = FALSE;
11031
11032     GameEnds(EndOfFile, NULL, GE_PLAYER);
11033     if(appData.serverMovesName != NULL) {
11034         /* [HGM] prepare to make moves file for broadcasting */
11035         clock_t t = clock();
11036         if(serverMoves != NULL) fclose(serverMoves);
11037         serverMoves = fopen(appData.serverMovesName, "r");
11038         if(serverMoves != NULL) {
11039             fclose(serverMoves);
11040             /* delay 15 sec before overwriting, so all clients can see end */
11041             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11042         }
11043         serverMoves = fopen(appData.serverMovesName, "w");
11044     }
11045
11046     ExitAnalyzeMode();
11047     gameMode = BeginningOfGame;
11048     ModeHighlight();
11049     if(appData.icsActive) gameInfo.variant = VariantNormal;
11050     currentMove = forwardMostMove = backwardMostMove = 0;
11051     MarkTargetSquares(1);
11052     InitPosition(redraw);
11053     for (i = 0; i < MAX_MOVES; i++) {
11054         if (commentList[i] != NULL) {
11055             free(commentList[i]);
11056             commentList[i] = NULL;
11057         }
11058     }
11059     ResetClocks();
11060     timeRemaining[0][0] = whiteTimeRemaining;
11061     timeRemaining[1][0] = blackTimeRemaining;
11062
11063     if (first.pr == NoProc) {
11064         StartChessProgram(&first);
11065     }
11066     if (init) {
11067             InitChessProgram(&first, startedFromSetupPosition);
11068     }
11069     DisplayTitle("");
11070     DisplayMessage("", "");
11071     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11072     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11073     ClearMap();        // [HGM] exclude: invalidate map
11074 }
11075
11076 void
11077 AutoPlayGameLoop ()
11078 {
11079     for (;;) {
11080         if (!AutoPlayOneMove())
11081           return;
11082         if (matchMode || appData.timeDelay == 0)
11083           continue;
11084         if (appData.timeDelay < 0)
11085           return;
11086         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11087         break;
11088     }
11089 }
11090
11091 void
11092 AnalyzeNextGame()
11093 {
11094     ReloadGame(1); // next game
11095 }
11096
11097 int
11098 AutoPlayOneMove ()
11099 {
11100     int fromX, fromY, toX, toY;
11101
11102     if (appData.debugMode) {
11103       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11104     }
11105
11106     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11107       return FALSE;
11108
11109     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11110       pvInfoList[currentMove].depth = programStats.depth;
11111       pvInfoList[currentMove].score = programStats.score;
11112       pvInfoList[currentMove].time  = 0;
11113       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11114     }
11115
11116     if (currentMove >= forwardMostMove) {
11117       if(gameMode == AnalyzeFile) {
11118           if(appData.loadGameIndex == -1) {
11119             GameEnds(EndOfFile, NULL, GE_FILE);
11120           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11121           } else {
11122           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11123         }
11124       }
11125 //      gameMode = EndOfGame;
11126 //      ModeHighlight();
11127
11128       /* [AS] Clear current move marker at the end of a game */
11129       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11130
11131       return FALSE;
11132     }
11133
11134     toX = moveList[currentMove][2] - AAA;
11135     toY = moveList[currentMove][3] - ONE;
11136
11137     if (moveList[currentMove][1] == '@') {
11138         if (appData.highlightLastMove) {
11139             SetHighlights(-1, -1, toX, toY);
11140         }
11141     } else {
11142         fromX = moveList[currentMove][0] - AAA;
11143         fromY = moveList[currentMove][1] - ONE;
11144
11145         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11146
11147         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11148
11149         if (appData.highlightLastMove) {
11150             SetHighlights(fromX, fromY, toX, toY);
11151         }
11152     }
11153     DisplayMove(currentMove);
11154     SendMoveToProgram(currentMove++, &first);
11155     DisplayBothClocks();
11156     DrawPosition(FALSE, boards[currentMove]);
11157     // [HGM] PV info: always display, routine tests if empty
11158     DisplayComment(currentMove - 1, commentList[currentMove]);
11159     return TRUE;
11160 }
11161
11162
11163 int
11164 LoadGameOneMove (ChessMove readAhead)
11165 {
11166     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11167     char promoChar = NULLCHAR;
11168     ChessMove moveType;
11169     char move[MSG_SIZ];
11170     char *p, *q;
11171
11172     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11173         gameMode != AnalyzeMode && gameMode != Training) {
11174         gameFileFP = NULL;
11175         return FALSE;
11176     }
11177
11178     yyboardindex = forwardMostMove;
11179     if (readAhead != EndOfFile) {
11180       moveType = readAhead;
11181     } else {
11182       if (gameFileFP == NULL)
11183           return FALSE;
11184       moveType = (ChessMove) Myylex();
11185     }
11186
11187     done = FALSE;
11188     switch (moveType) {
11189       case Comment:
11190         if (appData.debugMode)
11191           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11192         p = yy_text;
11193
11194         /* append the comment but don't display it */
11195         AppendComment(currentMove, p, FALSE);
11196         return TRUE;
11197
11198       case WhiteCapturesEnPassant:
11199       case BlackCapturesEnPassant:
11200       case WhitePromotion:
11201       case BlackPromotion:
11202       case WhiteNonPromotion:
11203       case BlackNonPromotion:
11204       case NormalMove:
11205       case WhiteKingSideCastle:
11206       case WhiteQueenSideCastle:
11207       case BlackKingSideCastle:
11208       case BlackQueenSideCastle:
11209       case WhiteKingSideCastleWild:
11210       case WhiteQueenSideCastleWild:
11211       case BlackKingSideCastleWild:
11212       case BlackQueenSideCastleWild:
11213       /* PUSH Fabien */
11214       case WhiteHSideCastleFR:
11215       case WhiteASideCastleFR:
11216       case BlackHSideCastleFR:
11217       case BlackASideCastleFR:
11218       /* POP Fabien */
11219         if (appData.debugMode)
11220           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11221         fromX = currentMoveString[0] - AAA;
11222         fromY = currentMoveString[1] - ONE;
11223         toX = currentMoveString[2] - AAA;
11224         toY = currentMoveString[3] - ONE;
11225         promoChar = currentMoveString[4];
11226         break;
11227
11228       case WhiteDrop:
11229       case BlackDrop:
11230         if (appData.debugMode)
11231           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11232         fromX = moveType == WhiteDrop ?
11233           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11234         (int) CharToPiece(ToLower(currentMoveString[0]));
11235         fromY = DROP_RANK;
11236         toX = currentMoveString[2] - AAA;
11237         toY = currentMoveString[3] - ONE;
11238         break;
11239
11240       case WhiteWins:
11241       case BlackWins:
11242       case GameIsDrawn:
11243       case GameUnfinished:
11244         if (appData.debugMode)
11245           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11246         p = strchr(yy_text, '{');
11247         if (p == NULL) p = strchr(yy_text, '(');
11248         if (p == NULL) {
11249             p = yy_text;
11250             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11251         } else {
11252             q = strchr(p, *p == '{' ? '}' : ')');
11253             if (q != NULL) *q = NULLCHAR;
11254             p++;
11255         }
11256         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11257         GameEnds(moveType, p, GE_FILE);
11258         done = TRUE;
11259         if (cmailMsgLoaded) {
11260             ClearHighlights();
11261             flipView = WhiteOnMove(currentMove);
11262             if (moveType == GameUnfinished) flipView = !flipView;
11263             if (appData.debugMode)
11264               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11265         }
11266         break;
11267
11268       case EndOfFile:
11269         if (appData.debugMode)
11270           fprintf(debugFP, "Parser hit end of file\n");
11271         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11272           case MT_NONE:
11273           case MT_CHECK:
11274             break;
11275           case MT_CHECKMATE:
11276           case MT_STAINMATE:
11277             if (WhiteOnMove(currentMove)) {
11278                 GameEnds(BlackWins, "Black mates", GE_FILE);
11279             } else {
11280                 GameEnds(WhiteWins, "White mates", GE_FILE);
11281             }
11282             break;
11283           case MT_STALEMATE:
11284             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11285             break;
11286         }
11287         done = TRUE;
11288         break;
11289
11290       case MoveNumberOne:
11291         if (lastLoadGameStart == GNUChessGame) {
11292             /* GNUChessGames have numbers, but they aren't move numbers */
11293             if (appData.debugMode)
11294               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11295                       yy_text, (int) moveType);
11296             return LoadGameOneMove(EndOfFile); /* tail recursion */
11297         }
11298         /* else fall thru */
11299
11300       case XBoardGame:
11301       case GNUChessGame:
11302       case PGNTag:
11303         /* Reached start of next game in file */
11304         if (appData.debugMode)
11305           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11306         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11307           case MT_NONE:
11308           case MT_CHECK:
11309             break;
11310           case MT_CHECKMATE:
11311           case MT_STAINMATE:
11312             if (WhiteOnMove(currentMove)) {
11313                 GameEnds(BlackWins, "Black mates", GE_FILE);
11314             } else {
11315                 GameEnds(WhiteWins, "White mates", GE_FILE);
11316             }
11317             break;
11318           case MT_STALEMATE:
11319             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11320             break;
11321         }
11322         done = TRUE;
11323         break;
11324
11325       case PositionDiagram:     /* should not happen; ignore */
11326       case ElapsedTime:         /* ignore */
11327       case NAG:                 /* ignore */
11328         if (appData.debugMode)
11329           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11330                   yy_text, (int) moveType);
11331         return LoadGameOneMove(EndOfFile); /* tail recursion */
11332
11333       case IllegalMove:
11334         if (appData.testLegality) {
11335             if (appData.debugMode)
11336               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11337             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11338                     (forwardMostMove / 2) + 1,
11339                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11340             DisplayError(move, 0);
11341             done = TRUE;
11342         } else {
11343             if (appData.debugMode)
11344               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11345                       yy_text, currentMoveString);
11346             fromX = currentMoveString[0] - AAA;
11347             fromY = currentMoveString[1] - ONE;
11348             toX = currentMoveString[2] - AAA;
11349             toY = currentMoveString[3] - ONE;
11350             promoChar = currentMoveString[4];
11351         }
11352         break;
11353
11354       case AmbiguousMove:
11355         if (appData.debugMode)
11356           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11357         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11358                 (forwardMostMove / 2) + 1,
11359                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11360         DisplayError(move, 0);
11361         done = TRUE;
11362         break;
11363
11364       default:
11365       case ImpossibleMove:
11366         if (appData.debugMode)
11367           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11368         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11369                 (forwardMostMove / 2) + 1,
11370                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11371         DisplayError(move, 0);
11372         done = TRUE;
11373         break;
11374     }
11375
11376     if (done) {
11377         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11378             DrawPosition(FALSE, boards[currentMove]);
11379             DisplayBothClocks();
11380             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11381               DisplayComment(currentMove - 1, commentList[currentMove]);
11382         }
11383         (void) StopLoadGameTimer();
11384         gameFileFP = NULL;
11385         cmailOldMove = forwardMostMove;
11386         return FALSE;
11387     } else {
11388         /* currentMoveString is set as a side-effect of yylex */
11389
11390         thinkOutput[0] = NULLCHAR;
11391         MakeMove(fromX, fromY, toX, toY, promoChar);
11392         currentMove = forwardMostMove;
11393         return TRUE;
11394     }
11395 }
11396
11397 /* Load the nth game from the given file */
11398 int
11399 LoadGameFromFile (char *filename, int n, char *title, int useList)
11400 {
11401     FILE *f;
11402     char buf[MSG_SIZ];
11403
11404     if (strcmp(filename, "-") == 0) {
11405         f = stdin;
11406         title = "stdin";
11407     } else {
11408         f = fopen(filename, "rb");
11409         if (f == NULL) {
11410           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11411             DisplayError(buf, errno);
11412             return FALSE;
11413         }
11414     }
11415     if (fseek(f, 0, 0) == -1) {
11416         /* f is not seekable; probably a pipe */
11417         useList = FALSE;
11418     }
11419     if (useList && n == 0) {
11420         int error = GameListBuild(f);
11421         if (error) {
11422             DisplayError(_("Cannot build game list"), error);
11423         } else if (!ListEmpty(&gameList) &&
11424                    ((ListGame *) gameList.tailPred)->number > 1) {
11425             GameListPopUp(f, title);
11426             return TRUE;
11427         }
11428         GameListDestroy();
11429         n = 1;
11430     }
11431     if (n == 0) n = 1;
11432     return LoadGame(f, n, title, FALSE);
11433 }
11434
11435
11436 void
11437 MakeRegisteredMove ()
11438 {
11439     int fromX, fromY, toX, toY;
11440     char promoChar;
11441     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11442         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11443           case CMAIL_MOVE:
11444           case CMAIL_DRAW:
11445             if (appData.debugMode)
11446               fprintf(debugFP, "Restoring %s for game %d\n",
11447                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11448
11449             thinkOutput[0] = NULLCHAR;
11450             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11451             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11452             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11453             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11454             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11455             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11456             MakeMove(fromX, fromY, toX, toY, promoChar);
11457             ShowMove(fromX, fromY, toX, toY);
11458
11459             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11460               case MT_NONE:
11461               case MT_CHECK:
11462                 break;
11463
11464               case MT_CHECKMATE:
11465               case MT_STAINMATE:
11466                 if (WhiteOnMove(currentMove)) {
11467                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11468                 } else {
11469                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11470                 }
11471                 break;
11472
11473               case MT_STALEMATE:
11474                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11475                 break;
11476             }
11477
11478             break;
11479
11480           case CMAIL_RESIGN:
11481             if (WhiteOnMove(currentMove)) {
11482                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11483             } else {
11484                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11485             }
11486             break;
11487
11488           case CMAIL_ACCEPT:
11489             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11490             break;
11491
11492           default:
11493             break;
11494         }
11495     }
11496
11497     return;
11498 }
11499
11500 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11501 int
11502 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11503 {
11504     int retVal;
11505
11506     if (gameNumber > nCmailGames) {
11507         DisplayError(_("No more games in this message"), 0);
11508         return FALSE;
11509     }
11510     if (f == lastLoadGameFP) {
11511         int offset = gameNumber - lastLoadGameNumber;
11512         if (offset == 0) {
11513             cmailMsg[0] = NULLCHAR;
11514             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11515                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11516                 nCmailMovesRegistered--;
11517             }
11518             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11519             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11520                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11521             }
11522         } else {
11523             if (! RegisterMove()) return FALSE;
11524         }
11525     }
11526
11527     retVal = LoadGame(f, gameNumber, title, useList);
11528
11529     /* Make move registered during previous look at this game, if any */
11530     MakeRegisteredMove();
11531
11532     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11533         commentList[currentMove]
11534           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11535         DisplayComment(currentMove - 1, commentList[currentMove]);
11536     }
11537
11538     return retVal;
11539 }
11540
11541 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11542 int
11543 ReloadGame (int offset)
11544 {
11545     int gameNumber = lastLoadGameNumber + offset;
11546     if (lastLoadGameFP == NULL) {
11547         DisplayError(_("No game has been loaded yet"), 0);
11548         return FALSE;
11549     }
11550     if (gameNumber <= 0) {
11551         DisplayError(_("Can't back up any further"), 0);
11552         return FALSE;
11553     }
11554     if (cmailMsgLoaded) {
11555         return CmailLoadGame(lastLoadGameFP, gameNumber,
11556                              lastLoadGameTitle, lastLoadGameUseList);
11557     } else {
11558         return LoadGame(lastLoadGameFP, gameNumber,
11559                         lastLoadGameTitle, lastLoadGameUseList);
11560     }
11561 }
11562
11563 int keys[EmptySquare+1];
11564
11565 int
11566 PositionMatches (Board b1, Board b2)
11567 {
11568     int r, f, sum=0;
11569     switch(appData.searchMode) {
11570         case 1: return CompareWithRights(b1, b2);
11571         case 2:
11572             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11573                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11574             }
11575             return TRUE;
11576         case 3:
11577             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11578               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11579                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11580             }
11581             return sum==0;
11582         case 4:
11583             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11584                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11585             }
11586             return sum==0;
11587     }
11588     return TRUE;
11589 }
11590
11591 #define Q_PROMO  4
11592 #define Q_EP     3
11593 #define Q_BCASTL 2
11594 #define Q_WCASTL 1
11595
11596 int pieceList[256], quickBoard[256];
11597 ChessSquare pieceType[256] = { EmptySquare };
11598 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11599 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11600 int soughtTotal, turn;
11601 Boolean epOK, flipSearch;
11602
11603 typedef struct {
11604     unsigned char piece, to;
11605 } Move;
11606
11607 #define DSIZE (250000)
11608
11609 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11610 Move *moveDatabase = initialSpace;
11611 unsigned int movePtr, dataSize = DSIZE;
11612
11613 int
11614 MakePieceList (Board board, int *counts)
11615 {
11616     int r, f, n=Q_PROMO, total=0;
11617     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11618     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11619         int sq = f + (r<<4);
11620         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11621             quickBoard[sq] = ++n;
11622             pieceList[n] = sq;
11623             pieceType[n] = board[r][f];
11624             counts[board[r][f]]++;
11625             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11626             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11627             total++;
11628         }
11629     }
11630     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11631     return total;
11632 }
11633
11634 void
11635 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11636 {
11637     int sq = fromX + (fromY<<4);
11638     int piece = quickBoard[sq];
11639     quickBoard[sq] = 0;
11640     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11641     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11642         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11643         moveDatabase[movePtr++].piece = Q_WCASTL;
11644         quickBoard[sq] = piece;
11645         piece = quickBoard[from]; quickBoard[from] = 0;
11646         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11647     } else
11648     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11649         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11650         moveDatabase[movePtr++].piece = Q_BCASTL;
11651         quickBoard[sq] = piece;
11652         piece = quickBoard[from]; quickBoard[from] = 0;
11653         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11654     } else
11655     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11656         quickBoard[(fromY<<4)+toX] = 0;
11657         moveDatabase[movePtr].piece = Q_EP;
11658         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11659         moveDatabase[movePtr].to = sq;
11660     } else
11661     if(promoPiece != pieceType[piece]) {
11662         moveDatabase[movePtr++].piece = Q_PROMO;
11663         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11664     }
11665     moveDatabase[movePtr].piece = piece;
11666     quickBoard[sq] = piece;
11667     movePtr++;
11668 }
11669
11670 int
11671 PackGame (Board board)
11672 {
11673     Move *newSpace = NULL;
11674     moveDatabase[movePtr].piece = 0; // terminate previous game
11675     if(movePtr > dataSize) {
11676         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11677         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11678         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11679         if(newSpace) {
11680             int i;
11681             Move *p = moveDatabase, *q = newSpace;
11682             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11683             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11684             moveDatabase = newSpace;
11685         } else { // calloc failed, we must be out of memory. Too bad...
11686             dataSize = 0; // prevent calloc events for all subsequent games
11687             return 0;     // and signal this one isn't cached
11688         }
11689     }
11690     movePtr++;
11691     MakePieceList(board, counts);
11692     return movePtr;
11693 }
11694
11695 int
11696 QuickCompare (Board board, int *minCounts, int *maxCounts)
11697 {   // compare according to search mode
11698     int r, f;
11699     switch(appData.searchMode)
11700     {
11701       case 1: // exact position match
11702         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11703         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11704             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11705         }
11706         break;
11707       case 2: // can have extra material on empty squares
11708         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11709             if(board[r][f] == EmptySquare) continue;
11710             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11711         }
11712         break;
11713       case 3: // material with exact Pawn structure
11714         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11715             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11716             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11717         } // fall through to material comparison
11718       case 4: // exact material
11719         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11720         break;
11721       case 6: // material range with given imbalance
11722         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11723         // fall through to range comparison
11724       case 5: // material range
11725         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11726     }
11727     return TRUE;
11728 }
11729
11730 int
11731 QuickScan (Board board, Move *move)
11732 {   // reconstruct game,and compare all positions in it
11733     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11734     do {
11735         int piece = move->piece;
11736         int to = move->to, from = pieceList[piece];
11737         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11738           if(!piece) return -1;
11739           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11740             piece = (++move)->piece;
11741             from = pieceList[piece];
11742             counts[pieceType[piece]]--;
11743             pieceType[piece] = (ChessSquare) move->to;
11744             counts[move->to]++;
11745           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11746             counts[pieceType[quickBoard[to]]]--;
11747             quickBoard[to] = 0; total--;
11748             move++;
11749             continue;
11750           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11751             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11752             from  = pieceList[piece]; // so this must be King
11753             quickBoard[from] = 0;
11754             pieceList[piece] = to;
11755             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11756             quickBoard[from] = 0; // rook
11757             quickBoard[to] = piece;
11758             to = move->to; piece = move->piece;
11759             goto aftercastle;
11760           }
11761         }
11762         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11763         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11764         quickBoard[from] = 0;
11765       aftercastle:
11766         quickBoard[to] = piece;
11767         pieceList[piece] = to;
11768         cnt++; turn ^= 3;
11769         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11770            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11771            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11772                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11773           ) {
11774             static int lastCounts[EmptySquare+1];
11775             int i;
11776             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11777             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11778         } else stretch = 0;
11779         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11780         move++;
11781     } while(1);
11782 }
11783
11784 void
11785 InitSearch ()
11786 {
11787     int r, f;
11788     flipSearch = FALSE;
11789     CopyBoard(soughtBoard, boards[currentMove]);
11790     soughtTotal = MakePieceList(soughtBoard, maxSought);
11791     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11792     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11793     CopyBoard(reverseBoard, boards[currentMove]);
11794     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11795         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11796         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11797         reverseBoard[r][f] = piece;
11798     }
11799     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11800     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11801     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11802                  || (boards[currentMove][CASTLING][2] == NoRights ||
11803                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11804                  && (boards[currentMove][CASTLING][5] == NoRights ||
11805                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11806       ) {
11807         flipSearch = TRUE;
11808         CopyBoard(flipBoard, soughtBoard);
11809         CopyBoard(rotateBoard, reverseBoard);
11810         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11811             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11812             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11813         }
11814     }
11815     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11816     if(appData.searchMode >= 5) {
11817         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11818         MakePieceList(soughtBoard, minSought);
11819         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11820     }
11821     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11822         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11823 }
11824
11825 GameInfo dummyInfo;
11826 static int creatingBook;
11827
11828 int
11829 GameContainsPosition (FILE *f, ListGame *lg)
11830 {
11831     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11832     int fromX, fromY, toX, toY;
11833     char promoChar;
11834     static int initDone=FALSE;
11835
11836     // weed out games based on numerical tag comparison
11837     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11838     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11839     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11840     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11841     if(!initDone) {
11842         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11843         initDone = TRUE;
11844     }
11845     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11846     else CopyBoard(boards[scratch], initialPosition); // default start position
11847     if(lg->moves) {
11848         turn = btm + 1;
11849         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11850         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11851     }
11852     if(btm) plyNr++;
11853     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11854     fseek(f, lg->offset, 0);
11855     yynewfile(f);
11856     while(1) {
11857         yyboardindex = scratch;
11858         quickFlag = plyNr+1;
11859         next = Myylex();
11860         quickFlag = 0;
11861         switch(next) {
11862             case PGNTag:
11863                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11864             default:
11865                 continue;
11866
11867             case XBoardGame:
11868             case GNUChessGame:
11869                 if(plyNr) return -1; // after we have seen moves, this is for new game
11870               continue;
11871
11872             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11873             case ImpossibleMove:
11874             case WhiteWins: // game ends here with these four
11875             case BlackWins:
11876             case GameIsDrawn:
11877             case GameUnfinished:
11878                 return -1;
11879
11880             case IllegalMove:
11881                 if(appData.testLegality) return -1;
11882             case WhiteCapturesEnPassant:
11883             case BlackCapturesEnPassant:
11884             case WhitePromotion:
11885             case BlackPromotion:
11886             case WhiteNonPromotion:
11887             case BlackNonPromotion:
11888             case NormalMove:
11889             case WhiteKingSideCastle:
11890             case WhiteQueenSideCastle:
11891             case BlackKingSideCastle:
11892             case BlackQueenSideCastle:
11893             case WhiteKingSideCastleWild:
11894             case WhiteQueenSideCastleWild:
11895             case BlackKingSideCastleWild:
11896             case BlackQueenSideCastleWild:
11897             case WhiteHSideCastleFR:
11898             case WhiteASideCastleFR:
11899             case BlackHSideCastleFR:
11900             case BlackASideCastleFR:
11901                 fromX = currentMoveString[0] - AAA;
11902                 fromY = currentMoveString[1] - ONE;
11903                 toX = currentMoveString[2] - AAA;
11904                 toY = currentMoveString[3] - ONE;
11905                 promoChar = currentMoveString[4];
11906                 break;
11907             case WhiteDrop:
11908             case BlackDrop:
11909                 fromX = next == WhiteDrop ?
11910                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11911                   (int) CharToPiece(ToLower(currentMoveString[0]));
11912                 fromY = DROP_RANK;
11913                 toX = currentMoveString[2] - AAA;
11914                 toY = currentMoveString[3] - ONE;
11915                 promoChar = 0;
11916                 break;
11917         }
11918         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11919         plyNr++;
11920         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11921         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11922         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11923         if(appData.findMirror) {
11924             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11925             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11926         }
11927     }
11928 }
11929
11930 /* Load the nth game from open file f */
11931 int
11932 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11933 {
11934     ChessMove cm;
11935     char buf[MSG_SIZ];
11936     int gn = gameNumber;
11937     ListGame *lg = NULL;
11938     int numPGNTags = 0;
11939     int err, pos = -1;
11940     GameMode oldGameMode;
11941     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11942
11943     if (appData.debugMode)
11944         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11945
11946     if (gameMode == Training )
11947         SetTrainingModeOff();
11948
11949     oldGameMode = gameMode;
11950     if (gameMode != BeginningOfGame) {
11951       Reset(FALSE, TRUE);
11952     }
11953
11954     gameFileFP = f;
11955     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11956         fclose(lastLoadGameFP);
11957     }
11958
11959     if (useList) {
11960         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11961
11962         if (lg) {
11963             fseek(f, lg->offset, 0);
11964             GameListHighlight(gameNumber);
11965             pos = lg->position;
11966             gn = 1;
11967         }
11968         else {
11969             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11970               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11971             else
11972             DisplayError(_("Game number out of range"), 0);
11973             return FALSE;
11974         }
11975     } else {
11976         GameListDestroy();
11977         if (fseek(f, 0, 0) == -1) {
11978             if (f == lastLoadGameFP ?
11979                 gameNumber == lastLoadGameNumber + 1 :
11980                 gameNumber == 1) {
11981                 gn = 1;
11982             } else {
11983                 DisplayError(_("Can't seek on game file"), 0);
11984                 return FALSE;
11985             }
11986         }
11987     }
11988     lastLoadGameFP = f;
11989     lastLoadGameNumber = gameNumber;
11990     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11991     lastLoadGameUseList = useList;
11992
11993     yynewfile(f);
11994
11995     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11996       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11997                 lg->gameInfo.black);
11998             DisplayTitle(buf);
11999     } else if (*title != NULLCHAR) {
12000         if (gameNumber > 1) {
12001           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12002             DisplayTitle(buf);
12003         } else {
12004             DisplayTitle(title);
12005         }
12006     }
12007
12008     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12009         gameMode = PlayFromGameFile;
12010         ModeHighlight();
12011     }
12012
12013     currentMove = forwardMostMove = backwardMostMove = 0;
12014     CopyBoard(boards[0], initialPosition);
12015     StopClocks();
12016
12017     /*
12018      * Skip the first gn-1 games in the file.
12019      * Also skip over anything that precedes an identifiable
12020      * start of game marker, to avoid being confused by
12021      * garbage at the start of the file.  Currently
12022      * recognized start of game markers are the move number "1",
12023      * the pattern "gnuchess .* game", the pattern
12024      * "^[#;%] [^ ]* game file", and a PGN tag block.
12025      * A game that starts with one of the latter two patterns
12026      * will also have a move number 1, possibly
12027      * following a position diagram.
12028      * 5-4-02: Let's try being more lenient and allowing a game to
12029      * start with an unnumbered move.  Does that break anything?
12030      */
12031     cm = lastLoadGameStart = EndOfFile;
12032     while (gn > 0) {
12033         yyboardindex = forwardMostMove;
12034         cm = (ChessMove) Myylex();
12035         switch (cm) {
12036           case EndOfFile:
12037             if (cmailMsgLoaded) {
12038                 nCmailGames = CMAIL_MAX_GAMES - gn;
12039             } else {
12040                 Reset(TRUE, TRUE);
12041                 DisplayError(_("Game not found in file"), 0);
12042             }
12043             return FALSE;
12044
12045           case GNUChessGame:
12046           case XBoardGame:
12047             gn--;
12048             lastLoadGameStart = cm;
12049             break;
12050
12051           case MoveNumberOne:
12052             switch (lastLoadGameStart) {
12053               case GNUChessGame:
12054               case XBoardGame:
12055               case PGNTag:
12056                 break;
12057               case MoveNumberOne:
12058               case EndOfFile:
12059                 gn--;           /* count this game */
12060                 lastLoadGameStart = cm;
12061                 break;
12062               default:
12063                 /* impossible */
12064                 break;
12065             }
12066             break;
12067
12068           case PGNTag:
12069             switch (lastLoadGameStart) {
12070               case GNUChessGame:
12071               case PGNTag:
12072               case MoveNumberOne:
12073               case EndOfFile:
12074                 gn--;           /* count this game */
12075                 lastLoadGameStart = cm;
12076                 break;
12077               case XBoardGame:
12078                 lastLoadGameStart = cm; /* game counted already */
12079                 break;
12080               default:
12081                 /* impossible */
12082                 break;
12083             }
12084             if (gn > 0) {
12085                 do {
12086                     yyboardindex = forwardMostMove;
12087                     cm = (ChessMove) Myylex();
12088                 } while (cm == PGNTag || cm == Comment);
12089             }
12090             break;
12091
12092           case WhiteWins:
12093           case BlackWins:
12094           case GameIsDrawn:
12095             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12096                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12097                     != CMAIL_OLD_RESULT) {
12098                     nCmailResults ++ ;
12099                     cmailResult[  CMAIL_MAX_GAMES
12100                                 - gn - 1] = CMAIL_OLD_RESULT;
12101                 }
12102             }
12103             break;
12104
12105           case NormalMove:
12106             /* Only a NormalMove can be at the start of a game
12107              * without a position diagram. */
12108             if (lastLoadGameStart == EndOfFile ) {
12109               gn--;
12110               lastLoadGameStart = MoveNumberOne;
12111             }
12112             break;
12113
12114           default:
12115             break;
12116         }
12117     }
12118
12119     if (appData.debugMode)
12120       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12121
12122     if (cm == XBoardGame) {
12123         /* Skip any header junk before position diagram and/or move 1 */
12124         for (;;) {
12125             yyboardindex = forwardMostMove;
12126             cm = (ChessMove) Myylex();
12127
12128             if (cm == EndOfFile ||
12129                 cm == GNUChessGame || cm == XBoardGame) {
12130                 /* Empty game; pretend end-of-file and handle later */
12131                 cm = EndOfFile;
12132                 break;
12133             }
12134
12135             if (cm == MoveNumberOne || cm == PositionDiagram ||
12136                 cm == PGNTag || cm == Comment)
12137               break;
12138         }
12139     } else if (cm == GNUChessGame) {
12140         if (gameInfo.event != NULL) {
12141             free(gameInfo.event);
12142         }
12143         gameInfo.event = StrSave(yy_text);
12144     }
12145
12146     startedFromSetupPosition = FALSE;
12147     while (cm == PGNTag) {
12148         if (appData.debugMode)
12149           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12150         err = ParsePGNTag(yy_text, &gameInfo);
12151         if (!err) numPGNTags++;
12152
12153         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12154         if(gameInfo.variant != oldVariant) {
12155             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12156             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12157             InitPosition(TRUE);
12158             oldVariant = gameInfo.variant;
12159             if (appData.debugMode)
12160               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12161         }
12162
12163
12164         if (gameInfo.fen != NULL) {
12165           Board initial_position;
12166           startedFromSetupPosition = TRUE;
12167           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12168             Reset(TRUE, TRUE);
12169             DisplayError(_("Bad FEN position in file"), 0);
12170             return FALSE;
12171           }
12172           CopyBoard(boards[0], initial_position);
12173           if (blackPlaysFirst) {
12174             currentMove = forwardMostMove = backwardMostMove = 1;
12175             CopyBoard(boards[1], initial_position);
12176             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12177             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12178             timeRemaining[0][1] = whiteTimeRemaining;
12179             timeRemaining[1][1] = blackTimeRemaining;
12180             if (commentList[0] != NULL) {
12181               commentList[1] = commentList[0];
12182               commentList[0] = NULL;
12183             }
12184           } else {
12185             currentMove = forwardMostMove = backwardMostMove = 0;
12186           }
12187           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12188           {   int i;
12189               initialRulePlies = FENrulePlies;
12190               for( i=0; i< nrCastlingRights; i++ )
12191                   initialRights[i] = initial_position[CASTLING][i];
12192           }
12193           yyboardindex = forwardMostMove;
12194           free(gameInfo.fen);
12195           gameInfo.fen = NULL;
12196         }
12197
12198         yyboardindex = forwardMostMove;
12199         cm = (ChessMove) Myylex();
12200
12201         /* Handle comments interspersed among the tags */
12202         while (cm == Comment) {
12203             char *p;
12204             if (appData.debugMode)
12205               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12206             p = yy_text;
12207             AppendComment(currentMove, p, FALSE);
12208             yyboardindex = forwardMostMove;
12209             cm = (ChessMove) Myylex();
12210         }
12211     }
12212
12213     /* don't rely on existence of Event tag since if game was
12214      * pasted from clipboard the Event tag may not exist
12215      */
12216     if (numPGNTags > 0){
12217         char *tags;
12218         if (gameInfo.variant == VariantNormal) {
12219           VariantClass v = StringToVariant(gameInfo.event);
12220           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12221           if(v < VariantShogi) gameInfo.variant = v;
12222         }
12223         if (!matchMode) {
12224           if( appData.autoDisplayTags ) {
12225             tags = PGNTags(&gameInfo);
12226             TagsPopUp(tags, CmailMsg());
12227             free(tags);
12228           }
12229         }
12230     } else {
12231         /* Make something up, but don't display it now */
12232         SetGameInfo();
12233         TagsPopDown();
12234     }
12235
12236     if (cm == PositionDiagram) {
12237         int i, j;
12238         char *p;
12239         Board initial_position;
12240
12241         if (appData.debugMode)
12242           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12243
12244         if (!startedFromSetupPosition) {
12245             p = yy_text;
12246             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12247               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12248                 switch (*p) {
12249                   case '{':
12250                   case '[':
12251                   case '-':
12252                   case ' ':
12253                   case '\t':
12254                   case '\n':
12255                   case '\r':
12256                     break;
12257                   default:
12258                     initial_position[i][j++] = CharToPiece(*p);
12259                     break;
12260                 }
12261             while (*p == ' ' || *p == '\t' ||
12262                    *p == '\n' || *p == '\r') p++;
12263
12264             if (strncmp(p, "black", strlen("black"))==0)
12265               blackPlaysFirst = TRUE;
12266             else
12267               blackPlaysFirst = FALSE;
12268             startedFromSetupPosition = TRUE;
12269
12270             CopyBoard(boards[0], initial_position);
12271             if (blackPlaysFirst) {
12272                 currentMove = forwardMostMove = backwardMostMove = 1;
12273                 CopyBoard(boards[1], initial_position);
12274                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12275                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12276                 timeRemaining[0][1] = whiteTimeRemaining;
12277                 timeRemaining[1][1] = blackTimeRemaining;
12278                 if (commentList[0] != NULL) {
12279                     commentList[1] = commentList[0];
12280                     commentList[0] = NULL;
12281                 }
12282             } else {
12283                 currentMove = forwardMostMove = backwardMostMove = 0;
12284             }
12285         }
12286         yyboardindex = forwardMostMove;
12287         cm = (ChessMove) Myylex();
12288     }
12289
12290   if(!creatingBook) {
12291     if (first.pr == NoProc) {
12292         StartChessProgram(&first);
12293     }
12294     InitChessProgram(&first, FALSE);
12295     SendToProgram("force\n", &first);
12296     if (startedFromSetupPosition) {
12297         SendBoard(&first, forwardMostMove);
12298     if (appData.debugMode) {
12299         fprintf(debugFP, "Load Game\n");
12300     }
12301         DisplayBothClocks();
12302     }
12303   }
12304
12305     /* [HGM] server: flag to write setup moves in broadcast file as one */
12306     loadFlag = appData.suppressLoadMoves;
12307
12308     while (cm == Comment) {
12309         char *p;
12310         if (appData.debugMode)
12311           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12312         p = yy_text;
12313         AppendComment(currentMove, p, FALSE);
12314         yyboardindex = forwardMostMove;
12315         cm = (ChessMove) Myylex();
12316     }
12317
12318     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12319         cm == WhiteWins || cm == BlackWins ||
12320         cm == GameIsDrawn || cm == GameUnfinished) {
12321         DisplayMessage("", _("No moves in game"));
12322         if (cmailMsgLoaded) {
12323             if (appData.debugMode)
12324               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12325             ClearHighlights();
12326             flipView = FALSE;
12327         }
12328         DrawPosition(FALSE, boards[currentMove]);
12329         DisplayBothClocks();
12330         gameMode = EditGame;
12331         ModeHighlight();
12332         gameFileFP = NULL;
12333         cmailOldMove = 0;
12334         return TRUE;
12335     }
12336
12337     // [HGM] PV info: routine tests if comment empty
12338     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12339         DisplayComment(currentMove - 1, commentList[currentMove]);
12340     }
12341     if (!matchMode && appData.timeDelay != 0)
12342       DrawPosition(FALSE, boards[currentMove]);
12343
12344     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12345       programStats.ok_to_send = 1;
12346     }
12347
12348     /* if the first token after the PGN tags is a move
12349      * and not move number 1, retrieve it from the parser
12350      */
12351     if (cm != MoveNumberOne)
12352         LoadGameOneMove(cm);
12353
12354     /* load the remaining moves from the file */
12355     while (LoadGameOneMove(EndOfFile)) {
12356       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12357       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12358     }
12359
12360     /* rewind to the start of the game */
12361     currentMove = backwardMostMove;
12362
12363     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12364
12365     if (oldGameMode == AnalyzeFile ||
12366         oldGameMode == AnalyzeMode) {
12367       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12368       AnalyzeFileEvent();
12369     }
12370
12371     if(creatingBook) return TRUE;
12372     if (!matchMode && pos > 0) {
12373         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12374     } else
12375     if (matchMode || appData.timeDelay == 0) {
12376       ToEndEvent();
12377     } else if (appData.timeDelay > 0) {
12378       AutoPlayGameLoop();
12379     }
12380
12381     if (appData.debugMode)
12382         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12383
12384     loadFlag = 0; /* [HGM] true game starts */
12385     return TRUE;
12386 }
12387
12388 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12389 int
12390 ReloadPosition (int offset)
12391 {
12392     int positionNumber = lastLoadPositionNumber + offset;
12393     if (lastLoadPositionFP == NULL) {
12394         DisplayError(_("No position has been loaded yet"), 0);
12395         return FALSE;
12396     }
12397     if (positionNumber <= 0) {
12398         DisplayError(_("Can't back up any further"), 0);
12399         return FALSE;
12400     }
12401     return LoadPosition(lastLoadPositionFP, positionNumber,
12402                         lastLoadPositionTitle);
12403 }
12404
12405 /* Load the nth position from the given file */
12406 int
12407 LoadPositionFromFile (char *filename, int n, char *title)
12408 {
12409     FILE *f;
12410     char buf[MSG_SIZ];
12411
12412     if (strcmp(filename, "-") == 0) {
12413         return LoadPosition(stdin, n, "stdin");
12414     } else {
12415         f = fopen(filename, "rb");
12416         if (f == NULL) {
12417             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12418             DisplayError(buf, errno);
12419             return FALSE;
12420         } else {
12421             return LoadPosition(f, n, title);
12422         }
12423     }
12424 }
12425
12426 /* Load the nth position from the given open file, and close it */
12427 int
12428 LoadPosition (FILE *f, int positionNumber, char *title)
12429 {
12430     char *p, line[MSG_SIZ];
12431     Board initial_position;
12432     int i, j, fenMode, pn;
12433
12434     if (gameMode == Training )
12435         SetTrainingModeOff();
12436
12437     if (gameMode != BeginningOfGame) {
12438         Reset(FALSE, TRUE);
12439     }
12440     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12441         fclose(lastLoadPositionFP);
12442     }
12443     if (positionNumber == 0) positionNumber = 1;
12444     lastLoadPositionFP = f;
12445     lastLoadPositionNumber = positionNumber;
12446     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12447     if (first.pr == NoProc && !appData.noChessProgram) {
12448       StartChessProgram(&first);
12449       InitChessProgram(&first, FALSE);
12450     }
12451     pn = positionNumber;
12452     if (positionNumber < 0) {
12453         /* Negative position number means to seek to that byte offset */
12454         if (fseek(f, -positionNumber, 0) == -1) {
12455             DisplayError(_("Can't seek on position file"), 0);
12456             return FALSE;
12457         };
12458         pn = 1;
12459     } else {
12460         if (fseek(f, 0, 0) == -1) {
12461             if (f == lastLoadPositionFP ?
12462                 positionNumber == lastLoadPositionNumber + 1 :
12463                 positionNumber == 1) {
12464                 pn = 1;
12465             } else {
12466                 DisplayError(_("Can't seek on position file"), 0);
12467                 return FALSE;
12468             }
12469         }
12470     }
12471     /* See if this file is FEN or old-style xboard */
12472     if (fgets(line, MSG_SIZ, f) == NULL) {
12473         DisplayError(_("Position not found in file"), 0);
12474         return FALSE;
12475     }
12476     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12477     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12478
12479     if (pn >= 2) {
12480         if (fenMode || line[0] == '#') pn--;
12481         while (pn > 0) {
12482             /* skip positions before number pn */
12483             if (fgets(line, MSG_SIZ, f) == NULL) {
12484                 Reset(TRUE, TRUE);
12485                 DisplayError(_("Position not found in file"), 0);
12486                 return FALSE;
12487             }
12488             if (fenMode || line[0] == '#') pn--;
12489         }
12490     }
12491
12492     if (fenMode) {
12493         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12494             DisplayError(_("Bad FEN position in file"), 0);
12495             return FALSE;
12496         }
12497     } else {
12498         (void) fgets(line, MSG_SIZ, f);
12499         (void) fgets(line, MSG_SIZ, f);
12500
12501         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12502             (void) fgets(line, MSG_SIZ, f);
12503             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12504                 if (*p == ' ')
12505                   continue;
12506                 initial_position[i][j++] = CharToPiece(*p);
12507             }
12508         }
12509
12510         blackPlaysFirst = FALSE;
12511         if (!feof(f)) {
12512             (void) fgets(line, MSG_SIZ, f);
12513             if (strncmp(line, "black", strlen("black"))==0)
12514               blackPlaysFirst = TRUE;
12515         }
12516     }
12517     startedFromSetupPosition = TRUE;
12518
12519     CopyBoard(boards[0], initial_position);
12520     if (blackPlaysFirst) {
12521         currentMove = forwardMostMove = backwardMostMove = 1;
12522         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12523         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12524         CopyBoard(boards[1], initial_position);
12525         DisplayMessage("", _("Black to play"));
12526     } else {
12527         currentMove = forwardMostMove = backwardMostMove = 0;
12528         DisplayMessage("", _("White to play"));
12529     }
12530     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12531     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12532         SendToProgram("force\n", &first);
12533         SendBoard(&first, forwardMostMove);
12534     }
12535     if (appData.debugMode) {
12536 int i, j;
12537   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12538   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12539         fprintf(debugFP, "Load Position\n");
12540     }
12541
12542     if (positionNumber > 1) {
12543       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12544         DisplayTitle(line);
12545     } else {
12546         DisplayTitle(title);
12547     }
12548     gameMode = EditGame;
12549     ModeHighlight();
12550     ResetClocks();
12551     timeRemaining[0][1] = whiteTimeRemaining;
12552     timeRemaining[1][1] = blackTimeRemaining;
12553     DrawPosition(FALSE, boards[currentMove]);
12554
12555     return TRUE;
12556 }
12557
12558
12559 void
12560 CopyPlayerNameIntoFileName (char **dest, char *src)
12561 {
12562     while (*src != NULLCHAR && *src != ',') {
12563         if (*src == ' ') {
12564             *(*dest)++ = '_';
12565             src++;
12566         } else {
12567             *(*dest)++ = *src++;
12568         }
12569     }
12570 }
12571
12572 char *
12573 DefaultFileName (char *ext)
12574 {
12575     static char def[MSG_SIZ];
12576     char *p;
12577
12578     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12579         p = def;
12580         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12581         *p++ = '-';
12582         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12583         *p++ = '.';
12584         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12585     } else {
12586         def[0] = NULLCHAR;
12587     }
12588     return def;
12589 }
12590
12591 /* Save the current game to the given file */
12592 int
12593 SaveGameToFile (char *filename, int append)
12594 {
12595     FILE *f;
12596     char buf[MSG_SIZ];
12597     int result, i, t,tot=0;
12598
12599     if (strcmp(filename, "-") == 0) {
12600         return SaveGame(stdout, 0, NULL);
12601     } else {
12602         for(i=0; i<10; i++) { // upto 10 tries
12603              f = fopen(filename, append ? "a" : "w");
12604              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12605              if(f || errno != 13) break;
12606              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12607              tot += t;
12608         }
12609         if (f == NULL) {
12610             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12611             DisplayError(buf, errno);
12612             return FALSE;
12613         } else {
12614             safeStrCpy(buf, lastMsg, MSG_SIZ);
12615             DisplayMessage(_("Waiting for access to save file"), "");
12616             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12617             DisplayMessage(_("Saving game"), "");
12618             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12619             result = SaveGame(f, 0, NULL);
12620             DisplayMessage(buf, "");
12621             return result;
12622         }
12623     }
12624 }
12625
12626 char *
12627 SavePart (char *str)
12628 {
12629     static char buf[MSG_SIZ];
12630     char *p;
12631
12632     p = strchr(str, ' ');
12633     if (p == NULL) return str;
12634     strncpy(buf, str, p - str);
12635     buf[p - str] = NULLCHAR;
12636     return buf;
12637 }
12638
12639 #define PGN_MAX_LINE 75
12640
12641 #define PGN_SIDE_WHITE  0
12642 #define PGN_SIDE_BLACK  1
12643
12644 static int
12645 FindFirstMoveOutOfBook (int side)
12646 {
12647     int result = -1;
12648
12649     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12650         int index = backwardMostMove;
12651         int has_book_hit = 0;
12652
12653         if( (index % 2) != side ) {
12654             index++;
12655         }
12656
12657         while( index < forwardMostMove ) {
12658             /* Check to see if engine is in book */
12659             int depth = pvInfoList[index].depth;
12660             int score = pvInfoList[index].score;
12661             int in_book = 0;
12662
12663             if( depth <= 2 ) {
12664                 in_book = 1;
12665             }
12666             else if( score == 0 && depth == 63 ) {
12667                 in_book = 1; /* Zappa */
12668             }
12669             else if( score == 2 && depth == 99 ) {
12670                 in_book = 1; /* Abrok */
12671             }
12672
12673             has_book_hit += in_book;
12674
12675             if( ! in_book ) {
12676                 result = index;
12677
12678                 break;
12679             }
12680
12681             index += 2;
12682         }
12683     }
12684
12685     return result;
12686 }
12687
12688 void
12689 GetOutOfBookInfo (char * buf)
12690 {
12691     int oob[2];
12692     int i;
12693     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12694
12695     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12696     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12697
12698     *buf = '\0';
12699
12700     if( oob[0] >= 0 || oob[1] >= 0 ) {
12701         for( i=0; i<2; i++ ) {
12702             int idx = oob[i];
12703
12704             if( idx >= 0 ) {
12705                 if( i > 0 && oob[0] >= 0 ) {
12706                     strcat( buf, "   " );
12707                 }
12708
12709                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12710                 sprintf( buf+strlen(buf), "%s%.2f",
12711                     pvInfoList[idx].score >= 0 ? "+" : "",
12712                     pvInfoList[idx].score / 100.0 );
12713             }
12714         }
12715     }
12716 }
12717
12718 /* Save game in PGN style and close the file */
12719 int
12720 SaveGamePGN (FILE *f)
12721 {
12722     int i, offset, linelen, newblock;
12723 //    char *movetext;
12724     char numtext[32];
12725     int movelen, numlen, blank;
12726     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12727
12728     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12729
12730     PrintPGNTags(f, &gameInfo);
12731
12732     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12733
12734     if (backwardMostMove > 0 || startedFromSetupPosition) {
12735         char *fen = PositionToFEN(backwardMostMove, NULL);
12736         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12737         fprintf(f, "\n{--------------\n");
12738         PrintPosition(f, backwardMostMove);
12739         fprintf(f, "--------------}\n");
12740         free(fen);
12741     }
12742     else {
12743         /* [AS] Out of book annotation */
12744         if( appData.saveOutOfBookInfo ) {
12745             char buf[64];
12746
12747             GetOutOfBookInfo( buf );
12748
12749             if( buf[0] != '\0' ) {
12750                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12751             }
12752         }
12753
12754         fprintf(f, "\n");
12755     }
12756
12757     i = backwardMostMove;
12758     linelen = 0;
12759     newblock = TRUE;
12760
12761     while (i < forwardMostMove) {
12762         /* Print comments preceding this move */
12763         if (commentList[i] != NULL) {
12764             if (linelen > 0) fprintf(f, "\n");
12765             fprintf(f, "%s", commentList[i]);
12766             linelen = 0;
12767             newblock = TRUE;
12768         }
12769
12770         /* Format move number */
12771         if ((i % 2) == 0)
12772           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12773         else
12774           if (newblock)
12775             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12776           else
12777             numtext[0] = NULLCHAR;
12778
12779         numlen = strlen(numtext);
12780         newblock = FALSE;
12781
12782         /* Print move number */
12783         blank = linelen > 0 && numlen > 0;
12784         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12785             fprintf(f, "\n");
12786             linelen = 0;
12787             blank = 0;
12788         }
12789         if (blank) {
12790             fprintf(f, " ");
12791             linelen++;
12792         }
12793         fprintf(f, "%s", numtext);
12794         linelen += numlen;
12795
12796         /* Get move */
12797         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12798         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12799
12800         /* Print move */
12801         blank = linelen > 0 && movelen > 0;
12802         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12803             fprintf(f, "\n");
12804             linelen = 0;
12805             blank = 0;
12806         }
12807         if (blank) {
12808             fprintf(f, " ");
12809             linelen++;
12810         }
12811         fprintf(f, "%s", move_buffer);
12812         linelen += movelen;
12813
12814         /* [AS] Add PV info if present */
12815         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12816             /* [HGM] add time */
12817             char buf[MSG_SIZ]; int seconds;
12818
12819             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12820
12821             if( seconds <= 0)
12822               buf[0] = 0;
12823             else
12824               if( seconds < 30 )
12825                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12826               else
12827                 {
12828                   seconds = (seconds + 4)/10; // round to full seconds
12829                   if( seconds < 60 )
12830                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12831                   else
12832                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12833                 }
12834
12835             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12836                       pvInfoList[i].score >= 0 ? "+" : "",
12837                       pvInfoList[i].score / 100.0,
12838                       pvInfoList[i].depth,
12839                       buf );
12840
12841             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12842
12843             /* Print score/depth */
12844             blank = linelen > 0 && movelen > 0;
12845             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12846                 fprintf(f, "\n");
12847                 linelen = 0;
12848                 blank = 0;
12849             }
12850             if (blank) {
12851                 fprintf(f, " ");
12852                 linelen++;
12853             }
12854             fprintf(f, "%s", move_buffer);
12855             linelen += movelen;
12856         }
12857
12858         i++;
12859     }
12860
12861     /* Start a new line */
12862     if (linelen > 0) fprintf(f, "\n");
12863
12864     /* Print comments after last move */
12865     if (commentList[i] != NULL) {
12866         fprintf(f, "%s\n", commentList[i]);
12867     }
12868
12869     /* Print result */
12870     if (gameInfo.resultDetails != NULL &&
12871         gameInfo.resultDetails[0] != NULLCHAR) {
12872         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12873                 PGNResult(gameInfo.result));
12874     } else {
12875         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12876     }
12877
12878     fclose(f);
12879     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12880     return TRUE;
12881 }
12882
12883 /* Save game in old style and close the file */
12884 int
12885 SaveGameOldStyle (FILE *f)
12886 {
12887     int i, offset;
12888     time_t tm;
12889
12890     tm = time((time_t *) NULL);
12891
12892     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12893     PrintOpponents(f);
12894
12895     if (backwardMostMove > 0 || startedFromSetupPosition) {
12896         fprintf(f, "\n[--------------\n");
12897         PrintPosition(f, backwardMostMove);
12898         fprintf(f, "--------------]\n");
12899     } else {
12900         fprintf(f, "\n");
12901     }
12902
12903     i = backwardMostMove;
12904     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12905
12906     while (i < forwardMostMove) {
12907         if (commentList[i] != NULL) {
12908             fprintf(f, "[%s]\n", commentList[i]);
12909         }
12910
12911         if ((i % 2) == 1) {
12912             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12913             i++;
12914         } else {
12915             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12916             i++;
12917             if (commentList[i] != NULL) {
12918                 fprintf(f, "\n");
12919                 continue;
12920             }
12921             if (i >= forwardMostMove) {
12922                 fprintf(f, "\n");
12923                 break;
12924             }
12925             fprintf(f, "%s\n", parseList[i]);
12926             i++;
12927         }
12928     }
12929
12930     if (commentList[i] != NULL) {
12931         fprintf(f, "[%s]\n", commentList[i]);
12932     }
12933
12934     /* This isn't really the old style, but it's close enough */
12935     if (gameInfo.resultDetails != NULL &&
12936         gameInfo.resultDetails[0] != NULLCHAR) {
12937         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12938                 gameInfo.resultDetails);
12939     } else {
12940         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12941     }
12942
12943     fclose(f);
12944     return TRUE;
12945 }
12946
12947 /* Save the current game to open file f and close the file */
12948 int
12949 SaveGame (FILE *f, int dummy, char *dummy2)
12950 {
12951     if (gameMode == EditPosition) EditPositionDone(TRUE);
12952     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12953     if (appData.oldSaveStyle)
12954       return SaveGameOldStyle(f);
12955     else
12956       return SaveGamePGN(f);
12957 }
12958
12959 /* Save the current position to the given file */
12960 int
12961 SavePositionToFile (char *filename)
12962 {
12963     FILE *f;
12964     char buf[MSG_SIZ];
12965
12966     if (strcmp(filename, "-") == 0) {
12967         return SavePosition(stdout, 0, NULL);
12968     } else {
12969         f = fopen(filename, "a");
12970         if (f == NULL) {
12971             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12972             DisplayError(buf, errno);
12973             return FALSE;
12974         } else {
12975             safeStrCpy(buf, lastMsg, MSG_SIZ);
12976             DisplayMessage(_("Waiting for access to save file"), "");
12977             flock(fileno(f), LOCK_EX); // [HGM] lock
12978             DisplayMessage(_("Saving position"), "");
12979             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12980             SavePosition(f, 0, NULL);
12981             DisplayMessage(buf, "");
12982             return TRUE;
12983         }
12984     }
12985 }
12986
12987 /* Save the current position to the given open file and close the file */
12988 int
12989 SavePosition (FILE *f, int dummy, char *dummy2)
12990 {
12991     time_t tm;
12992     char *fen;
12993
12994     if (gameMode == EditPosition) EditPositionDone(TRUE);
12995     if (appData.oldSaveStyle) {
12996         tm = time((time_t *) NULL);
12997
12998         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12999         PrintOpponents(f);
13000         fprintf(f, "[--------------\n");
13001         PrintPosition(f, currentMove);
13002         fprintf(f, "--------------]\n");
13003     } else {
13004         fen = PositionToFEN(currentMove, NULL);
13005         fprintf(f, "%s\n", fen);
13006         free(fen);
13007     }
13008     fclose(f);
13009     return TRUE;
13010 }
13011
13012 void
13013 ReloadCmailMsgEvent (int unregister)
13014 {
13015 #if !WIN32
13016     static char *inFilename = NULL;
13017     static char *outFilename;
13018     int i;
13019     struct stat inbuf, outbuf;
13020     int status;
13021
13022     /* Any registered moves are unregistered if unregister is set, */
13023     /* i.e. invoked by the signal handler */
13024     if (unregister) {
13025         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13026             cmailMoveRegistered[i] = FALSE;
13027             if (cmailCommentList[i] != NULL) {
13028                 free(cmailCommentList[i]);
13029                 cmailCommentList[i] = NULL;
13030             }
13031         }
13032         nCmailMovesRegistered = 0;
13033     }
13034
13035     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13036         cmailResult[i] = CMAIL_NOT_RESULT;
13037     }
13038     nCmailResults = 0;
13039
13040     if (inFilename == NULL) {
13041         /* Because the filenames are static they only get malloced once  */
13042         /* and they never get freed                                      */
13043         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13044         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13045
13046         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13047         sprintf(outFilename, "%s.out", appData.cmailGameName);
13048     }
13049
13050     status = stat(outFilename, &outbuf);
13051     if (status < 0) {
13052         cmailMailedMove = FALSE;
13053     } else {
13054         status = stat(inFilename, &inbuf);
13055         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13056     }
13057
13058     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13059        counts the games, notes how each one terminated, etc.
13060
13061        It would be nice to remove this kludge and instead gather all
13062        the information while building the game list.  (And to keep it
13063        in the game list nodes instead of having a bunch of fixed-size
13064        parallel arrays.)  Note this will require getting each game's
13065        termination from the PGN tags, as the game list builder does
13066        not process the game moves.  --mann
13067        */
13068     cmailMsgLoaded = TRUE;
13069     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13070
13071     /* Load first game in the file or popup game menu */
13072     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13073
13074 #endif /* !WIN32 */
13075     return;
13076 }
13077
13078 int
13079 RegisterMove ()
13080 {
13081     FILE *f;
13082     char string[MSG_SIZ];
13083
13084     if (   cmailMailedMove
13085         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13086         return TRUE;            /* Allow free viewing  */
13087     }
13088
13089     /* Unregister move to ensure that we don't leave RegisterMove        */
13090     /* with the move registered when the conditions for registering no   */
13091     /* longer hold                                                       */
13092     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13093         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13094         nCmailMovesRegistered --;
13095
13096         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13097           {
13098               free(cmailCommentList[lastLoadGameNumber - 1]);
13099               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13100           }
13101     }
13102
13103     if (cmailOldMove == -1) {
13104         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13105         return FALSE;
13106     }
13107
13108     if (currentMove > cmailOldMove + 1) {
13109         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13110         return FALSE;
13111     }
13112
13113     if (currentMove < cmailOldMove) {
13114         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13115         return FALSE;
13116     }
13117
13118     if (forwardMostMove > currentMove) {
13119         /* Silently truncate extra moves */
13120         TruncateGame();
13121     }
13122
13123     if (   (currentMove == cmailOldMove + 1)
13124         || (   (currentMove == cmailOldMove)
13125             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13126                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13127         if (gameInfo.result != GameUnfinished) {
13128             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13129         }
13130
13131         if (commentList[currentMove] != NULL) {
13132             cmailCommentList[lastLoadGameNumber - 1]
13133               = StrSave(commentList[currentMove]);
13134         }
13135         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13136
13137         if (appData.debugMode)
13138           fprintf(debugFP, "Saving %s for game %d\n",
13139                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13140
13141         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13142
13143         f = fopen(string, "w");
13144         if (appData.oldSaveStyle) {
13145             SaveGameOldStyle(f); /* also closes the file */
13146
13147             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13148             f = fopen(string, "w");
13149             SavePosition(f, 0, NULL); /* also closes the file */
13150         } else {
13151             fprintf(f, "{--------------\n");
13152             PrintPosition(f, currentMove);
13153             fprintf(f, "--------------}\n\n");
13154
13155             SaveGame(f, 0, NULL); /* also closes the file*/
13156         }
13157
13158         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13159         nCmailMovesRegistered ++;
13160     } else if (nCmailGames == 1) {
13161         DisplayError(_("You have not made a move yet"), 0);
13162         return FALSE;
13163     }
13164
13165     return TRUE;
13166 }
13167
13168 void
13169 MailMoveEvent ()
13170 {
13171 #if !WIN32
13172     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13173     FILE *commandOutput;
13174     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13175     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13176     int nBuffers;
13177     int i;
13178     int archived;
13179     char *arcDir;
13180
13181     if (! cmailMsgLoaded) {
13182         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13183         return;
13184     }
13185
13186     if (nCmailGames == nCmailResults) {
13187         DisplayError(_("No unfinished games"), 0);
13188         return;
13189     }
13190
13191 #if CMAIL_PROHIBIT_REMAIL
13192     if (cmailMailedMove) {
13193       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);
13194         DisplayError(msg, 0);
13195         return;
13196     }
13197 #endif
13198
13199     if (! (cmailMailedMove || RegisterMove())) return;
13200
13201     if (   cmailMailedMove
13202         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13203       snprintf(string, MSG_SIZ, partCommandString,
13204                appData.debugMode ? " -v" : "", appData.cmailGameName);
13205         commandOutput = popen(string, "r");
13206
13207         if (commandOutput == NULL) {
13208             DisplayError(_("Failed to invoke cmail"), 0);
13209         } else {
13210             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13211                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13212             }
13213             if (nBuffers > 1) {
13214                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13215                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13216                 nBytes = MSG_SIZ - 1;
13217             } else {
13218                 (void) memcpy(msg, buffer, nBytes);
13219             }
13220             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13221
13222             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13223                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13224
13225                 archived = TRUE;
13226                 for (i = 0; i < nCmailGames; i ++) {
13227                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13228                         archived = FALSE;
13229                     }
13230                 }
13231                 if (   archived
13232                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13233                         != NULL)) {
13234                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13235                            arcDir,
13236                            appData.cmailGameName,
13237                            gameInfo.date);
13238                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13239                     cmailMsgLoaded = FALSE;
13240                 }
13241             }
13242
13243             DisplayInformation(msg);
13244             pclose(commandOutput);
13245         }
13246     } else {
13247         if ((*cmailMsg) != '\0') {
13248             DisplayInformation(cmailMsg);
13249         }
13250     }
13251
13252     return;
13253 #endif /* !WIN32 */
13254 }
13255
13256 char *
13257 CmailMsg ()
13258 {
13259 #if WIN32
13260     return NULL;
13261 #else
13262     int  prependComma = 0;
13263     char number[5];
13264     char string[MSG_SIZ];       /* Space for game-list */
13265     int  i;
13266
13267     if (!cmailMsgLoaded) return "";
13268
13269     if (cmailMailedMove) {
13270       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13271     } else {
13272         /* Create a list of games left */
13273       snprintf(string, MSG_SIZ, "[");
13274         for (i = 0; i < nCmailGames; i ++) {
13275             if (! (   cmailMoveRegistered[i]
13276                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13277                 if (prependComma) {
13278                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13279                 } else {
13280                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13281                     prependComma = 1;
13282                 }
13283
13284                 strcat(string, number);
13285             }
13286         }
13287         strcat(string, "]");
13288
13289         if (nCmailMovesRegistered + nCmailResults == 0) {
13290             switch (nCmailGames) {
13291               case 1:
13292                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13293                 break;
13294
13295               case 2:
13296                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13297                 break;
13298
13299               default:
13300                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13301                          nCmailGames);
13302                 break;
13303             }
13304         } else {
13305             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13306               case 1:
13307                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13308                          string);
13309                 break;
13310
13311               case 0:
13312                 if (nCmailResults == nCmailGames) {
13313                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13314                 } else {
13315                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13316                 }
13317                 break;
13318
13319               default:
13320                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13321                          string);
13322             }
13323         }
13324     }
13325     return cmailMsg;
13326 #endif /* WIN32 */
13327 }
13328
13329 void
13330 ResetGameEvent ()
13331 {
13332     if (gameMode == Training)
13333       SetTrainingModeOff();
13334
13335     Reset(TRUE, TRUE);
13336     cmailMsgLoaded = FALSE;
13337     if (appData.icsActive) {
13338       SendToICS(ics_prefix);
13339       SendToICS("refresh\n");
13340     }
13341 }
13342
13343 void
13344 ExitEvent (int status)
13345 {
13346     exiting++;
13347     if (exiting > 2) {
13348       /* Give up on clean exit */
13349       exit(status);
13350     }
13351     if (exiting > 1) {
13352       /* Keep trying for clean exit */
13353       return;
13354     }
13355
13356     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13357
13358     if (telnetISR != NULL) {
13359       RemoveInputSource(telnetISR);
13360     }
13361     if (icsPR != NoProc) {
13362       DestroyChildProcess(icsPR, TRUE);
13363     }
13364
13365     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13366     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13367
13368     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13369     /* make sure this other one finishes before killing it!                  */
13370     if(endingGame) { int count = 0;
13371         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13372         while(endingGame && count++ < 10) DoSleep(1);
13373         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13374     }
13375
13376     /* Kill off chess programs */
13377     if (first.pr != NoProc) {
13378         ExitAnalyzeMode();
13379
13380         DoSleep( appData.delayBeforeQuit );
13381         SendToProgram("quit\n", &first);
13382         DoSleep( appData.delayAfterQuit );
13383         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13384     }
13385     if (second.pr != NoProc) {
13386         DoSleep( appData.delayBeforeQuit );
13387         SendToProgram("quit\n", &second);
13388         DoSleep( appData.delayAfterQuit );
13389         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13390     }
13391     if (first.isr != NULL) {
13392         RemoveInputSource(first.isr);
13393     }
13394     if (second.isr != NULL) {
13395         RemoveInputSource(second.isr);
13396     }
13397
13398     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13399     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13400
13401     ShutDownFrontEnd();
13402     exit(status);
13403 }
13404
13405 void
13406 PauseEngine (ChessProgramState *cps)
13407 {
13408     SendToProgram("pause\n", cps);
13409     cps->pause = 2;
13410 }
13411
13412 void
13413 UnPauseEngine (ChessProgramState *cps)
13414 {
13415     SendToProgram("resume\n", cps);
13416     cps->pause = 1;
13417 }
13418
13419 void
13420 PauseEvent ()
13421 {
13422     if (appData.debugMode)
13423         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13424     if (pausing) {
13425         pausing = FALSE;
13426         ModeHighlight();
13427         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13428             StartClocks();
13429             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13430                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13431                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13432             }
13433             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13434             HandleMachineMove(stashedInputMove, stalledEngine);
13435             stalledEngine = NULL;
13436             return;
13437         }
13438         if (gameMode == MachinePlaysWhite ||
13439             gameMode == TwoMachinesPlay   ||
13440             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13441             if(first.pause)  UnPauseEngine(&first);
13442             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13443             if(second.pause) UnPauseEngine(&second);
13444             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13445             StartClocks();
13446         } else {
13447             DisplayBothClocks();
13448         }
13449         if (gameMode == PlayFromGameFile) {
13450             if (appData.timeDelay >= 0)
13451                 AutoPlayGameLoop();
13452         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13453             Reset(FALSE, TRUE);
13454             SendToICS(ics_prefix);
13455             SendToICS("refresh\n");
13456         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13457             ForwardInner(forwardMostMove);
13458         }
13459         pauseExamInvalid = FALSE;
13460     } else {
13461         switch (gameMode) {
13462           default:
13463             return;
13464           case IcsExamining:
13465             pauseExamForwardMostMove = forwardMostMove;
13466             pauseExamInvalid = FALSE;
13467             /* fall through */
13468           case IcsObserving:
13469           case IcsPlayingWhite:
13470           case IcsPlayingBlack:
13471             pausing = TRUE;
13472             ModeHighlight();
13473             return;
13474           case PlayFromGameFile:
13475             (void) StopLoadGameTimer();
13476             pausing = TRUE;
13477             ModeHighlight();
13478             break;
13479           case BeginningOfGame:
13480             if (appData.icsActive) return;
13481             /* else fall through */
13482           case MachinePlaysWhite:
13483           case MachinePlaysBlack:
13484           case TwoMachinesPlay:
13485             if (forwardMostMove == 0)
13486               return;           /* don't pause if no one has moved */
13487             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13488                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13489                 if(onMove->pause) {           // thinking engine can be paused
13490                     PauseEngine(onMove);      // do it
13491                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13492                         PauseEngine(onMove->other);
13493                     else
13494                         SendToProgram("easy\n", onMove->other);
13495                     StopClocks();
13496                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13497             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13498                 if(first.pause) {
13499                     PauseEngine(&first);
13500                     StopClocks();
13501                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13502             } else { // human on move, pause pondering by either method
13503                 if(first.pause)
13504                     PauseEngine(&first);
13505                 else if(appData.ponderNextMove)
13506                     SendToProgram("easy\n", &first);
13507                 StopClocks();
13508             }
13509             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13510           case AnalyzeMode:
13511             pausing = TRUE;
13512             ModeHighlight();
13513             break;
13514         }
13515     }
13516 }
13517
13518 void
13519 EditCommentEvent ()
13520 {
13521     char title[MSG_SIZ];
13522
13523     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13524       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13525     } else {
13526       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13527                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13528                parseList[currentMove - 1]);
13529     }
13530
13531     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13532 }
13533
13534
13535 void
13536 EditTagsEvent ()
13537 {
13538     char *tags = PGNTags(&gameInfo);
13539     bookUp = FALSE;
13540     EditTagsPopUp(tags, NULL);
13541     free(tags);
13542 }
13543
13544 void
13545 ToggleSecond ()
13546 {
13547   if(second.analyzing) {
13548     SendToProgram("exit\n", &second);
13549     second.analyzing = FALSE;
13550   } else {
13551     if (second.pr == NoProc) StartChessProgram(&second);
13552     InitChessProgram(&second, FALSE);
13553     FeedMovesToProgram(&second, currentMove);
13554
13555     SendToProgram("analyze\n", &second);
13556     second.analyzing = TRUE;
13557   }
13558 }
13559
13560 /* Toggle ShowThinking */
13561 void
13562 ToggleShowThinking()
13563 {
13564   appData.showThinking = !appData.showThinking;
13565   ShowThinkingEvent();
13566 }
13567
13568 int
13569 AnalyzeModeEvent ()
13570 {
13571     char buf[MSG_SIZ];
13572
13573     if (!first.analysisSupport) {
13574       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13575       DisplayError(buf, 0);
13576       return 0;
13577     }
13578     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13579     if (appData.icsActive) {
13580         if (gameMode != IcsObserving) {
13581           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13582             DisplayError(buf, 0);
13583             /* secure check */
13584             if (appData.icsEngineAnalyze) {
13585                 if (appData.debugMode)
13586                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13587                 ExitAnalyzeMode();
13588                 ModeHighlight();
13589             }
13590             return 0;
13591         }
13592         /* if enable, user wants to disable icsEngineAnalyze */
13593         if (appData.icsEngineAnalyze) {
13594                 ExitAnalyzeMode();
13595                 ModeHighlight();
13596                 return 0;
13597         }
13598         appData.icsEngineAnalyze = TRUE;
13599         if (appData.debugMode)
13600             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13601     }
13602
13603     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13604     if (appData.noChessProgram || gameMode == AnalyzeMode)
13605       return 0;
13606
13607     if (gameMode != AnalyzeFile) {
13608         if (!appData.icsEngineAnalyze) {
13609                EditGameEvent();
13610                if (gameMode != EditGame) return 0;
13611         }
13612         if (!appData.showThinking) ToggleShowThinking();
13613         ResurrectChessProgram();
13614         SendToProgram("analyze\n", &first);
13615         first.analyzing = TRUE;
13616         /*first.maybeThinking = TRUE;*/
13617         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13618         EngineOutputPopUp();
13619     }
13620     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13621     pausing = FALSE;
13622     ModeHighlight();
13623     SetGameInfo();
13624
13625     StartAnalysisClock();
13626     GetTimeMark(&lastNodeCountTime);
13627     lastNodeCount = 0;
13628     return 1;
13629 }
13630
13631 void
13632 AnalyzeFileEvent ()
13633 {
13634     if (appData.noChessProgram || gameMode == AnalyzeFile)
13635       return;
13636
13637     if (!first.analysisSupport) {
13638       char buf[MSG_SIZ];
13639       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13640       DisplayError(buf, 0);
13641       return;
13642     }
13643
13644     if (gameMode != AnalyzeMode) {
13645         keepInfo = 1; // mere annotating should not alter PGN tags
13646         EditGameEvent();
13647         keepInfo = 0;
13648         if (gameMode != EditGame) return;
13649         if (!appData.showThinking) ToggleShowThinking();
13650         ResurrectChessProgram();
13651         SendToProgram("analyze\n", &first);
13652         first.analyzing = TRUE;
13653         /*first.maybeThinking = TRUE;*/
13654         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13655         EngineOutputPopUp();
13656     }
13657     gameMode = AnalyzeFile;
13658     pausing = FALSE;
13659     ModeHighlight();
13660
13661     StartAnalysisClock();
13662     GetTimeMark(&lastNodeCountTime);
13663     lastNodeCount = 0;
13664     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13665     AnalysisPeriodicEvent(1);
13666 }
13667
13668 void
13669 MachineWhiteEvent ()
13670 {
13671     char buf[MSG_SIZ];
13672     char *bookHit = NULL;
13673
13674     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13675       return;
13676
13677
13678     if (gameMode == PlayFromGameFile ||
13679         gameMode == TwoMachinesPlay  ||
13680         gameMode == Training         ||
13681         gameMode == AnalyzeMode      ||
13682         gameMode == EndOfGame)
13683         EditGameEvent();
13684
13685     if (gameMode == EditPosition)
13686         EditPositionDone(TRUE);
13687
13688     if (!WhiteOnMove(currentMove)) {
13689         DisplayError(_("It is not White's turn"), 0);
13690         return;
13691     }
13692
13693     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13694       ExitAnalyzeMode();
13695
13696     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13697         gameMode == AnalyzeFile)
13698         TruncateGame();
13699
13700     ResurrectChessProgram();    /* in case it isn't running */
13701     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13702         gameMode = MachinePlaysWhite;
13703         ResetClocks();
13704     } else
13705     gameMode = MachinePlaysWhite;
13706     pausing = FALSE;
13707     ModeHighlight();
13708     SetGameInfo();
13709     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13710     DisplayTitle(buf);
13711     if (first.sendName) {
13712       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13713       SendToProgram(buf, &first);
13714     }
13715     if (first.sendTime) {
13716       if (first.useColors) {
13717         SendToProgram("black\n", &first); /*gnu kludge*/
13718       }
13719       SendTimeRemaining(&first, TRUE);
13720     }
13721     if (first.useColors) {
13722       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13723     }
13724     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13725     SetMachineThinkingEnables();
13726     first.maybeThinking = TRUE;
13727     StartClocks();
13728     firstMove = FALSE;
13729
13730     if (appData.autoFlipView && !flipView) {
13731       flipView = !flipView;
13732       DrawPosition(FALSE, NULL);
13733       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13734     }
13735
13736     if(bookHit) { // [HGM] book: simulate book reply
13737         static char bookMove[MSG_SIZ]; // a bit generous?
13738
13739         programStats.nodes = programStats.depth = programStats.time =
13740         programStats.score = programStats.got_only_move = 0;
13741         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13742
13743         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13744         strcat(bookMove, bookHit);
13745         HandleMachineMove(bookMove, &first);
13746     }
13747 }
13748
13749 void
13750 MachineBlackEvent ()
13751 {
13752   char buf[MSG_SIZ];
13753   char *bookHit = NULL;
13754
13755     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13756         return;
13757
13758
13759     if (gameMode == PlayFromGameFile ||
13760         gameMode == TwoMachinesPlay  ||
13761         gameMode == Training         ||
13762         gameMode == AnalyzeMode      ||
13763         gameMode == EndOfGame)
13764         EditGameEvent();
13765
13766     if (gameMode == EditPosition)
13767         EditPositionDone(TRUE);
13768
13769     if (WhiteOnMove(currentMove)) {
13770         DisplayError(_("It is not Black's turn"), 0);
13771         return;
13772     }
13773
13774     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13775       ExitAnalyzeMode();
13776
13777     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13778         gameMode == AnalyzeFile)
13779         TruncateGame();
13780
13781     ResurrectChessProgram();    /* in case it isn't running */
13782     gameMode = MachinePlaysBlack;
13783     pausing = FALSE;
13784     ModeHighlight();
13785     SetGameInfo();
13786     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13787     DisplayTitle(buf);
13788     if (first.sendName) {
13789       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13790       SendToProgram(buf, &first);
13791     }
13792     if (first.sendTime) {
13793       if (first.useColors) {
13794         SendToProgram("white\n", &first); /*gnu kludge*/
13795       }
13796       SendTimeRemaining(&first, FALSE);
13797     }
13798     if (first.useColors) {
13799       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13800     }
13801     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13802     SetMachineThinkingEnables();
13803     first.maybeThinking = TRUE;
13804     StartClocks();
13805
13806     if (appData.autoFlipView && flipView) {
13807       flipView = !flipView;
13808       DrawPosition(FALSE, NULL);
13809       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13810     }
13811     if(bookHit) { // [HGM] book: simulate book reply
13812         static char bookMove[MSG_SIZ]; // a bit generous?
13813
13814         programStats.nodes = programStats.depth = programStats.time =
13815         programStats.score = programStats.got_only_move = 0;
13816         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13817
13818         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13819         strcat(bookMove, bookHit);
13820         HandleMachineMove(bookMove, &first);
13821     }
13822 }
13823
13824
13825 void
13826 DisplayTwoMachinesTitle ()
13827 {
13828     char buf[MSG_SIZ];
13829     if (appData.matchGames > 0) {
13830         if(appData.tourneyFile[0]) {
13831           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13832                    gameInfo.white, _("vs."), gameInfo.black,
13833                    nextGame+1, appData.matchGames+1,
13834                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13835         } else
13836         if (first.twoMachinesColor[0] == 'w') {
13837           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13838                    gameInfo.white, _("vs."),  gameInfo.black,
13839                    first.matchWins, second.matchWins,
13840                    matchGame - 1 - (first.matchWins + second.matchWins));
13841         } else {
13842           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13843                    gameInfo.white, _("vs."), gameInfo.black,
13844                    second.matchWins, first.matchWins,
13845                    matchGame - 1 - (first.matchWins + second.matchWins));
13846         }
13847     } else {
13848       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13849     }
13850     DisplayTitle(buf);
13851 }
13852
13853 void
13854 SettingsMenuIfReady ()
13855 {
13856   if (second.lastPing != second.lastPong) {
13857     DisplayMessage("", _("Waiting for second chess program"));
13858     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13859     return;
13860   }
13861   ThawUI();
13862   DisplayMessage("", "");
13863   SettingsPopUp(&second);
13864 }
13865
13866 int
13867 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13868 {
13869     char buf[MSG_SIZ];
13870     if (cps->pr == NoProc) {
13871         StartChessProgram(cps);
13872         if (cps->protocolVersion == 1) {
13873           retry();
13874         } else {
13875           /* kludge: allow timeout for initial "feature" command */
13876           FreezeUI();
13877           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13878           DisplayMessage("", buf);
13879           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13880         }
13881         return 1;
13882     }
13883     return 0;
13884 }
13885
13886 void
13887 TwoMachinesEvent P((void))
13888 {
13889     int i;
13890     char buf[MSG_SIZ];
13891     ChessProgramState *onmove;
13892     char *bookHit = NULL;
13893     static int stalling = 0;
13894     TimeMark now;
13895     long wait;
13896
13897     if (appData.noChessProgram) return;
13898
13899     switch (gameMode) {
13900       case TwoMachinesPlay:
13901         return;
13902       case MachinePlaysWhite:
13903       case MachinePlaysBlack:
13904         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13905             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13906             return;
13907         }
13908         /* fall through */
13909       case BeginningOfGame:
13910       case PlayFromGameFile:
13911       case EndOfGame:
13912         EditGameEvent();
13913         if (gameMode != EditGame) return;
13914         break;
13915       case EditPosition:
13916         EditPositionDone(TRUE);
13917         break;
13918       case AnalyzeMode:
13919       case AnalyzeFile:
13920         ExitAnalyzeMode();
13921         break;
13922       case EditGame:
13923       default:
13924         break;
13925     }
13926
13927 //    forwardMostMove = currentMove;
13928     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13929
13930     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13931
13932     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13933     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13934       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13935       return;
13936     }
13937
13938     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13939         DisplayError("second engine does not play this", 0);
13940         return;
13941     }
13942
13943     if(!stalling) {
13944       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13945       SendToProgram("force\n", &second);
13946       stalling = 1;
13947       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13948       return;
13949     }
13950     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13951     if(appData.matchPause>10000 || appData.matchPause<10)
13952                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13953     wait = SubtractTimeMarks(&now, &pauseStart);
13954     if(wait < appData.matchPause) {
13955         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13956         return;
13957     }
13958     // we are now committed to starting the game
13959     stalling = 0;
13960     DisplayMessage("", "");
13961     if (startedFromSetupPosition) {
13962         SendBoard(&second, backwardMostMove);
13963     if (appData.debugMode) {
13964         fprintf(debugFP, "Two Machines\n");
13965     }
13966     }
13967     for (i = backwardMostMove; i < forwardMostMove; i++) {
13968         SendMoveToProgram(i, &second);
13969     }
13970
13971     gameMode = TwoMachinesPlay;
13972     pausing = FALSE;
13973     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13974     SetGameInfo();
13975     DisplayTwoMachinesTitle();
13976     firstMove = TRUE;
13977     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13978         onmove = &first;
13979     } else {
13980         onmove = &second;
13981     }
13982     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13983     SendToProgram(first.computerString, &first);
13984     if (first.sendName) {
13985       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13986       SendToProgram(buf, &first);
13987     }
13988     SendToProgram(second.computerString, &second);
13989     if (second.sendName) {
13990       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13991       SendToProgram(buf, &second);
13992     }
13993
13994     ResetClocks();
13995     if (!first.sendTime || !second.sendTime) {
13996         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13997         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13998     }
13999     if (onmove->sendTime) {
14000       if (onmove->useColors) {
14001         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14002       }
14003       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14004     }
14005     if (onmove->useColors) {
14006       SendToProgram(onmove->twoMachinesColor, onmove);
14007     }
14008     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14009 //    SendToProgram("go\n", onmove);
14010     onmove->maybeThinking = TRUE;
14011     SetMachineThinkingEnables();
14012
14013     StartClocks();
14014
14015     if(bookHit) { // [HGM] book: simulate book reply
14016         static char bookMove[MSG_SIZ]; // a bit generous?
14017
14018         programStats.nodes = programStats.depth = programStats.time =
14019         programStats.score = programStats.got_only_move = 0;
14020         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14021
14022         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14023         strcat(bookMove, bookHit);
14024         savedMessage = bookMove; // args for deferred call
14025         savedState = onmove;
14026         ScheduleDelayedEvent(DeferredBookMove, 1);
14027     }
14028 }
14029
14030 void
14031 TrainingEvent ()
14032 {
14033     if (gameMode == Training) {
14034       SetTrainingModeOff();
14035       gameMode = PlayFromGameFile;
14036       DisplayMessage("", _("Training mode off"));
14037     } else {
14038       gameMode = Training;
14039       animateTraining = appData.animate;
14040
14041       /* make sure we are not already at the end of the game */
14042       if (currentMove < forwardMostMove) {
14043         SetTrainingModeOn();
14044         DisplayMessage("", _("Training mode on"));
14045       } else {
14046         gameMode = PlayFromGameFile;
14047         DisplayError(_("Already at end of game"), 0);
14048       }
14049     }
14050     ModeHighlight();
14051 }
14052
14053 void
14054 IcsClientEvent ()
14055 {
14056     if (!appData.icsActive) return;
14057     switch (gameMode) {
14058       case IcsPlayingWhite:
14059       case IcsPlayingBlack:
14060       case IcsObserving:
14061       case IcsIdle:
14062       case BeginningOfGame:
14063       case IcsExamining:
14064         return;
14065
14066       case EditGame:
14067         break;
14068
14069       case EditPosition:
14070         EditPositionDone(TRUE);
14071         break;
14072
14073       case AnalyzeMode:
14074       case AnalyzeFile:
14075         ExitAnalyzeMode();
14076         break;
14077
14078       default:
14079         EditGameEvent();
14080         break;
14081     }
14082
14083     gameMode = IcsIdle;
14084     ModeHighlight();
14085     return;
14086 }
14087
14088 void
14089 EditGameEvent ()
14090 {
14091     int i;
14092
14093     switch (gameMode) {
14094       case Training:
14095         SetTrainingModeOff();
14096         break;
14097       case MachinePlaysWhite:
14098       case MachinePlaysBlack:
14099       case BeginningOfGame:
14100         SendToProgram("force\n", &first);
14101         SetUserThinkingEnables();
14102         break;
14103       case PlayFromGameFile:
14104         (void) StopLoadGameTimer();
14105         if (gameFileFP != NULL) {
14106             gameFileFP = NULL;
14107         }
14108         break;
14109       case EditPosition:
14110         EditPositionDone(TRUE);
14111         break;
14112       case AnalyzeMode:
14113       case AnalyzeFile:
14114         ExitAnalyzeMode();
14115         SendToProgram("force\n", &first);
14116         break;
14117       case TwoMachinesPlay:
14118         GameEnds(EndOfFile, NULL, GE_PLAYER);
14119         ResurrectChessProgram();
14120         SetUserThinkingEnables();
14121         break;
14122       case EndOfGame:
14123         ResurrectChessProgram();
14124         break;
14125       case IcsPlayingBlack:
14126       case IcsPlayingWhite:
14127         DisplayError(_("Warning: You are still playing a game"), 0);
14128         break;
14129       case IcsObserving:
14130         DisplayError(_("Warning: You are still observing a game"), 0);
14131         break;
14132       case IcsExamining:
14133         DisplayError(_("Warning: You are still examining a game"), 0);
14134         break;
14135       case IcsIdle:
14136         break;
14137       case EditGame:
14138       default:
14139         return;
14140     }
14141
14142     pausing = FALSE;
14143     StopClocks();
14144     first.offeredDraw = second.offeredDraw = 0;
14145
14146     if (gameMode == PlayFromGameFile) {
14147         whiteTimeRemaining = timeRemaining[0][currentMove];
14148         blackTimeRemaining = timeRemaining[1][currentMove];
14149         DisplayTitle("");
14150     }
14151
14152     if (gameMode == MachinePlaysWhite ||
14153         gameMode == MachinePlaysBlack ||
14154         gameMode == TwoMachinesPlay ||
14155         gameMode == EndOfGame) {
14156         i = forwardMostMove;
14157         while (i > currentMove) {
14158             SendToProgram("undo\n", &first);
14159             i--;
14160         }
14161         if(!adjustedClock) {
14162         whiteTimeRemaining = timeRemaining[0][currentMove];
14163         blackTimeRemaining = timeRemaining[1][currentMove];
14164         DisplayBothClocks();
14165         }
14166         if (whiteFlag || blackFlag) {
14167             whiteFlag = blackFlag = 0;
14168         }
14169         DisplayTitle("");
14170     }
14171
14172     gameMode = EditGame;
14173     ModeHighlight();
14174     SetGameInfo();
14175 }
14176
14177
14178 void
14179 EditPositionEvent ()
14180 {
14181     if (gameMode == EditPosition) {
14182         EditGameEvent();
14183         return;
14184     }
14185
14186     EditGameEvent();
14187     if (gameMode != EditGame) return;
14188
14189     gameMode = EditPosition;
14190     ModeHighlight();
14191     SetGameInfo();
14192     if (currentMove > 0)
14193       CopyBoard(boards[0], boards[currentMove]);
14194
14195     blackPlaysFirst = !WhiteOnMove(currentMove);
14196     ResetClocks();
14197     currentMove = forwardMostMove = backwardMostMove = 0;
14198     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14199     DisplayMove(-1);
14200     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14201 }
14202
14203 void
14204 ExitAnalyzeMode ()
14205 {
14206     /* [DM] icsEngineAnalyze - possible call from other functions */
14207     if (appData.icsEngineAnalyze) {
14208         appData.icsEngineAnalyze = FALSE;
14209
14210         DisplayMessage("",_("Close ICS engine analyze..."));
14211     }
14212     if (first.analysisSupport && first.analyzing) {
14213       SendToBoth("exit\n");
14214       first.analyzing = second.analyzing = FALSE;
14215     }
14216     thinkOutput[0] = NULLCHAR;
14217 }
14218
14219 void
14220 EditPositionDone (Boolean fakeRights)
14221 {
14222     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14223
14224     startedFromSetupPosition = TRUE;
14225     InitChessProgram(&first, FALSE);
14226     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14227       boards[0][EP_STATUS] = EP_NONE;
14228       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14229       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14230         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14231         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14232       } else boards[0][CASTLING][2] = NoRights;
14233       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14234         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14235         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14236       } else boards[0][CASTLING][5] = NoRights;
14237       if(gameInfo.variant == VariantSChess) {
14238         int i;
14239         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14240           boards[0][VIRGIN][i] = 0;
14241           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14242           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14243         }
14244       }
14245     }
14246     SendToProgram("force\n", &first);
14247     if (blackPlaysFirst) {
14248         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14249         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14250         currentMove = forwardMostMove = backwardMostMove = 1;
14251         CopyBoard(boards[1], boards[0]);
14252     } else {
14253         currentMove = forwardMostMove = backwardMostMove = 0;
14254     }
14255     SendBoard(&first, forwardMostMove);
14256     if (appData.debugMode) {
14257         fprintf(debugFP, "EditPosDone\n");
14258     }
14259     DisplayTitle("");
14260     DisplayMessage("", "");
14261     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14262     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14263     gameMode = EditGame;
14264     ModeHighlight();
14265     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14266     ClearHighlights(); /* [AS] */
14267 }
14268
14269 /* Pause for `ms' milliseconds */
14270 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14271 void
14272 TimeDelay (long ms)
14273 {
14274     TimeMark m1, m2;
14275
14276     GetTimeMark(&m1);
14277     do {
14278         GetTimeMark(&m2);
14279     } while (SubtractTimeMarks(&m2, &m1) < ms);
14280 }
14281
14282 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14283 void
14284 SendMultiLineToICS (char *buf)
14285 {
14286     char temp[MSG_SIZ+1], *p;
14287     int len;
14288
14289     len = strlen(buf);
14290     if (len > MSG_SIZ)
14291       len = MSG_SIZ;
14292
14293     strncpy(temp, buf, len);
14294     temp[len] = 0;
14295
14296     p = temp;
14297     while (*p) {
14298         if (*p == '\n' || *p == '\r')
14299           *p = ' ';
14300         ++p;
14301     }
14302
14303     strcat(temp, "\n");
14304     SendToICS(temp);
14305     SendToPlayer(temp, strlen(temp));
14306 }
14307
14308 void
14309 SetWhiteToPlayEvent ()
14310 {
14311     if (gameMode == EditPosition) {
14312         blackPlaysFirst = FALSE;
14313         DisplayBothClocks();    /* works because currentMove is 0 */
14314     } else if (gameMode == IcsExamining) {
14315         SendToICS(ics_prefix);
14316         SendToICS("tomove white\n");
14317     }
14318 }
14319
14320 void
14321 SetBlackToPlayEvent ()
14322 {
14323     if (gameMode == EditPosition) {
14324         blackPlaysFirst = TRUE;
14325         currentMove = 1;        /* kludge */
14326         DisplayBothClocks();
14327         currentMove = 0;
14328     } else if (gameMode == IcsExamining) {
14329         SendToICS(ics_prefix);
14330         SendToICS("tomove black\n");
14331     }
14332 }
14333
14334 void
14335 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14336 {
14337     char buf[MSG_SIZ];
14338     ChessSquare piece = boards[0][y][x];
14339
14340     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14341
14342     switch (selection) {
14343       case ClearBoard:
14344         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14345             SendToICS(ics_prefix);
14346             SendToICS("bsetup clear\n");
14347         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14348             SendToICS(ics_prefix);
14349             SendToICS("clearboard\n");
14350         } else {
14351             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14352                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14353                 for (y = 0; y < BOARD_HEIGHT; y++) {
14354                     if (gameMode == IcsExamining) {
14355                         if (boards[currentMove][y][x] != EmptySquare) {
14356                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14357                                     AAA + x, ONE + y);
14358                             SendToICS(buf);
14359                         }
14360                     } else {
14361                         boards[0][y][x] = p;
14362                     }
14363                 }
14364             }
14365         }
14366         if (gameMode == EditPosition) {
14367             DrawPosition(FALSE, boards[0]);
14368         }
14369         break;
14370
14371       case WhitePlay:
14372         SetWhiteToPlayEvent();
14373         break;
14374
14375       case BlackPlay:
14376         SetBlackToPlayEvent();
14377         break;
14378
14379       case EmptySquare:
14380         if (gameMode == IcsExamining) {
14381             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14382             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14383             SendToICS(buf);
14384         } else {
14385             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14386                 if(x == BOARD_LEFT-2) {
14387                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14388                     boards[0][y][1] = 0;
14389                 } else
14390                 if(x == BOARD_RGHT+1) {
14391                     if(y >= gameInfo.holdingsSize) break;
14392                     boards[0][y][BOARD_WIDTH-2] = 0;
14393                 } else break;
14394             }
14395             boards[0][y][x] = EmptySquare;
14396             DrawPosition(FALSE, boards[0]);
14397         }
14398         break;
14399
14400       case PromotePiece:
14401         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14402            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14403             selection = (ChessSquare) (PROMOTED piece);
14404         } else if(piece == EmptySquare) selection = WhiteSilver;
14405         else selection = (ChessSquare)((int)piece - 1);
14406         goto defaultlabel;
14407
14408       case DemotePiece:
14409         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14410            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14411             selection = (ChessSquare) (DEMOTED piece);
14412         } else if(piece == EmptySquare) selection = BlackSilver;
14413         else selection = (ChessSquare)((int)piece + 1);
14414         goto defaultlabel;
14415
14416       case WhiteQueen:
14417       case BlackQueen:
14418         if(gameInfo.variant == VariantShatranj ||
14419            gameInfo.variant == VariantXiangqi  ||
14420            gameInfo.variant == VariantCourier  ||
14421            gameInfo.variant == VariantMakruk     )
14422             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14423         goto defaultlabel;
14424
14425       case WhiteKing:
14426       case BlackKing:
14427         if(gameInfo.variant == VariantXiangqi)
14428             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14429         if(gameInfo.variant == VariantKnightmate)
14430             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14431       default:
14432         defaultlabel:
14433         if (gameMode == IcsExamining) {
14434             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14435             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14436                      PieceToChar(selection), AAA + x, ONE + y);
14437             SendToICS(buf);
14438         } else {
14439             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14440                 int n;
14441                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14442                     n = PieceToNumber(selection - BlackPawn);
14443                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14444                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14445                     boards[0][BOARD_HEIGHT-1-n][1]++;
14446                 } else
14447                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14448                     n = PieceToNumber(selection);
14449                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14450                     boards[0][n][BOARD_WIDTH-1] = selection;
14451                     boards[0][n][BOARD_WIDTH-2]++;
14452                 }
14453             } else
14454             boards[0][y][x] = selection;
14455             DrawPosition(TRUE, boards[0]);
14456             ClearHighlights();
14457             fromX = fromY = -1;
14458         }
14459         break;
14460     }
14461 }
14462
14463
14464 void
14465 DropMenuEvent (ChessSquare selection, int x, int y)
14466 {
14467     ChessMove moveType;
14468
14469     switch (gameMode) {
14470       case IcsPlayingWhite:
14471       case MachinePlaysBlack:
14472         if (!WhiteOnMove(currentMove)) {
14473             DisplayMoveError(_("It is Black's turn"));
14474             return;
14475         }
14476         moveType = WhiteDrop;
14477         break;
14478       case IcsPlayingBlack:
14479       case MachinePlaysWhite:
14480         if (WhiteOnMove(currentMove)) {
14481             DisplayMoveError(_("It is White's turn"));
14482             return;
14483         }
14484         moveType = BlackDrop;
14485         break;
14486       case EditGame:
14487         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14488         break;
14489       default:
14490         return;
14491     }
14492
14493     if (moveType == BlackDrop && selection < BlackPawn) {
14494       selection = (ChessSquare) ((int) selection
14495                                  + (int) BlackPawn - (int) WhitePawn);
14496     }
14497     if (boards[currentMove][y][x] != EmptySquare) {
14498         DisplayMoveError(_("That square is occupied"));
14499         return;
14500     }
14501
14502     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14503 }
14504
14505 void
14506 AcceptEvent ()
14507 {
14508     /* Accept a pending offer of any kind from opponent */
14509
14510     if (appData.icsActive) {
14511         SendToICS(ics_prefix);
14512         SendToICS("accept\n");
14513     } else if (cmailMsgLoaded) {
14514         if (currentMove == cmailOldMove &&
14515             commentList[cmailOldMove] != NULL &&
14516             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14517                    "Black offers a draw" : "White offers a draw")) {
14518             TruncateGame();
14519             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14520             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14521         } else {
14522             DisplayError(_("There is no pending offer on this move"), 0);
14523             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14524         }
14525     } else {
14526         /* Not used for offers from chess program */
14527     }
14528 }
14529
14530 void
14531 DeclineEvent ()
14532 {
14533     /* Decline a pending offer of any kind from opponent */
14534
14535     if (appData.icsActive) {
14536         SendToICS(ics_prefix);
14537         SendToICS("decline\n");
14538     } else if (cmailMsgLoaded) {
14539         if (currentMove == cmailOldMove &&
14540             commentList[cmailOldMove] != NULL &&
14541             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14542                    "Black offers a draw" : "White offers a draw")) {
14543 #ifdef NOTDEF
14544             AppendComment(cmailOldMove, "Draw declined", TRUE);
14545             DisplayComment(cmailOldMove - 1, "Draw declined");
14546 #endif /*NOTDEF*/
14547         } else {
14548             DisplayError(_("There is no pending offer on this move"), 0);
14549         }
14550     } else {
14551         /* Not used for offers from chess program */
14552     }
14553 }
14554
14555 void
14556 RematchEvent ()
14557 {
14558     /* Issue ICS rematch command */
14559     if (appData.icsActive) {
14560         SendToICS(ics_prefix);
14561         SendToICS("rematch\n");
14562     }
14563 }
14564
14565 void
14566 CallFlagEvent ()
14567 {
14568     /* Call your opponent's flag (claim a win on time) */
14569     if (appData.icsActive) {
14570         SendToICS(ics_prefix);
14571         SendToICS("flag\n");
14572     } else {
14573         switch (gameMode) {
14574           default:
14575             return;
14576           case MachinePlaysWhite:
14577             if (whiteFlag) {
14578                 if (blackFlag)
14579                   GameEnds(GameIsDrawn, "Both players ran out of time",
14580                            GE_PLAYER);
14581                 else
14582                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14583             } else {
14584                 DisplayError(_("Your opponent is not out of time"), 0);
14585             }
14586             break;
14587           case MachinePlaysBlack:
14588             if (blackFlag) {
14589                 if (whiteFlag)
14590                   GameEnds(GameIsDrawn, "Both players ran out of time",
14591                            GE_PLAYER);
14592                 else
14593                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14594             } else {
14595                 DisplayError(_("Your opponent is not out of time"), 0);
14596             }
14597             break;
14598         }
14599     }
14600 }
14601
14602 void
14603 ClockClick (int which)
14604 {       // [HGM] code moved to back-end from winboard.c
14605         if(which) { // black clock
14606           if (gameMode == EditPosition || gameMode == IcsExamining) {
14607             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14608             SetBlackToPlayEvent();
14609           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14610           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14611           } else if (shiftKey) {
14612             AdjustClock(which, -1);
14613           } else if (gameMode == IcsPlayingWhite ||
14614                      gameMode == MachinePlaysBlack) {
14615             CallFlagEvent();
14616           }
14617         } else { // white clock
14618           if (gameMode == EditPosition || gameMode == IcsExamining) {
14619             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14620             SetWhiteToPlayEvent();
14621           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14622           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14623           } else if (shiftKey) {
14624             AdjustClock(which, -1);
14625           } else if (gameMode == IcsPlayingBlack ||
14626                    gameMode == MachinePlaysWhite) {
14627             CallFlagEvent();
14628           }
14629         }
14630 }
14631
14632 void
14633 DrawEvent ()
14634 {
14635     /* Offer draw or accept pending draw offer from opponent */
14636
14637     if (appData.icsActive) {
14638         /* Note: tournament rules require draw offers to be
14639            made after you make your move but before you punch
14640            your clock.  Currently ICS doesn't let you do that;
14641            instead, you immediately punch your clock after making
14642            a move, but you can offer a draw at any time. */
14643
14644         SendToICS(ics_prefix);
14645         SendToICS("draw\n");
14646         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14647     } else if (cmailMsgLoaded) {
14648         if (currentMove == cmailOldMove &&
14649             commentList[cmailOldMove] != NULL &&
14650             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14651                    "Black offers a draw" : "White offers a draw")) {
14652             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14653             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14654         } else if (currentMove == cmailOldMove + 1) {
14655             char *offer = WhiteOnMove(cmailOldMove) ?
14656               "White offers a draw" : "Black offers a draw";
14657             AppendComment(currentMove, offer, TRUE);
14658             DisplayComment(currentMove - 1, offer);
14659             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14660         } else {
14661             DisplayError(_("You must make your move before offering a draw"), 0);
14662             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14663         }
14664     } else if (first.offeredDraw) {
14665         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14666     } else {
14667         if (first.sendDrawOffers) {
14668             SendToProgram("draw\n", &first);
14669             userOfferedDraw = TRUE;
14670         }
14671     }
14672 }
14673
14674 void
14675 AdjournEvent ()
14676 {
14677     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14678
14679     if (appData.icsActive) {
14680         SendToICS(ics_prefix);
14681         SendToICS("adjourn\n");
14682     } else {
14683         /* Currently GNU Chess doesn't offer or accept Adjourns */
14684     }
14685 }
14686
14687
14688 void
14689 AbortEvent ()
14690 {
14691     /* Offer Abort or accept pending Abort offer from opponent */
14692
14693     if (appData.icsActive) {
14694         SendToICS(ics_prefix);
14695         SendToICS("abort\n");
14696     } else {
14697         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14698     }
14699 }
14700
14701 void
14702 ResignEvent ()
14703 {
14704     /* Resign.  You can do this even if it's not your turn. */
14705
14706     if (appData.icsActive) {
14707         SendToICS(ics_prefix);
14708         SendToICS("resign\n");
14709     } else {
14710         switch (gameMode) {
14711           case MachinePlaysWhite:
14712             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14713             break;
14714           case MachinePlaysBlack:
14715             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14716             break;
14717           case EditGame:
14718             if (cmailMsgLoaded) {
14719                 TruncateGame();
14720                 if (WhiteOnMove(cmailOldMove)) {
14721                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14722                 } else {
14723                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14724                 }
14725                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14726             }
14727             break;
14728           default:
14729             break;
14730         }
14731     }
14732 }
14733
14734
14735 void
14736 StopObservingEvent ()
14737 {
14738     /* Stop observing current games */
14739     SendToICS(ics_prefix);
14740     SendToICS("unobserve\n");
14741 }
14742
14743 void
14744 StopExaminingEvent ()
14745 {
14746     /* Stop observing current game */
14747     SendToICS(ics_prefix);
14748     SendToICS("unexamine\n");
14749 }
14750
14751 void
14752 ForwardInner (int target)
14753 {
14754     int limit; int oldSeekGraphUp = seekGraphUp;
14755
14756     if (appData.debugMode)
14757         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14758                 target, currentMove, forwardMostMove);
14759
14760     if (gameMode == EditPosition)
14761       return;
14762
14763     seekGraphUp = FALSE;
14764     MarkTargetSquares(1);
14765
14766     if (gameMode == PlayFromGameFile && !pausing)
14767       PauseEvent();
14768
14769     if (gameMode == IcsExamining && pausing)
14770       limit = pauseExamForwardMostMove;
14771     else
14772       limit = forwardMostMove;
14773
14774     if (target > limit) target = limit;
14775
14776     if (target > 0 && moveList[target - 1][0]) {
14777         int fromX, fromY, toX, toY;
14778         toX = moveList[target - 1][2] - AAA;
14779         toY = moveList[target - 1][3] - ONE;
14780         if (moveList[target - 1][1] == '@') {
14781             if (appData.highlightLastMove) {
14782                 SetHighlights(-1, -1, toX, toY);
14783             }
14784         } else {
14785             fromX = moveList[target - 1][0] - AAA;
14786             fromY = moveList[target - 1][1] - ONE;
14787             if (target == currentMove + 1) {
14788                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14789             }
14790             if (appData.highlightLastMove) {
14791                 SetHighlights(fromX, fromY, toX, toY);
14792             }
14793         }
14794     }
14795     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14796         gameMode == Training || gameMode == PlayFromGameFile ||
14797         gameMode == AnalyzeFile) {
14798         while (currentMove < target) {
14799             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14800             SendMoveToProgram(currentMove++, &first);
14801         }
14802     } else {
14803         currentMove = target;
14804     }
14805
14806     if (gameMode == EditGame || gameMode == EndOfGame) {
14807         whiteTimeRemaining = timeRemaining[0][currentMove];
14808         blackTimeRemaining = timeRemaining[1][currentMove];
14809     }
14810     DisplayBothClocks();
14811     DisplayMove(currentMove - 1);
14812     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14813     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14814     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14815         DisplayComment(currentMove - 1, commentList[currentMove]);
14816     }
14817     ClearMap(); // [HGM] exclude: invalidate map
14818 }
14819
14820
14821 void
14822 ForwardEvent ()
14823 {
14824     if (gameMode == IcsExamining && !pausing) {
14825         SendToICS(ics_prefix);
14826         SendToICS("forward\n");
14827     } else {
14828         ForwardInner(currentMove + 1);
14829     }
14830 }
14831
14832 void
14833 ToEndEvent ()
14834 {
14835     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14836         /* to optimze, we temporarily turn off analysis mode while we feed
14837          * the remaining moves to the engine. Otherwise we get analysis output
14838          * after each move.
14839          */
14840         if (first.analysisSupport) {
14841           SendToProgram("exit\nforce\n", &first);
14842           first.analyzing = FALSE;
14843         }
14844     }
14845
14846     if (gameMode == IcsExamining && !pausing) {
14847         SendToICS(ics_prefix);
14848         SendToICS("forward 999999\n");
14849     } else {
14850         ForwardInner(forwardMostMove);
14851     }
14852
14853     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14854         /* we have fed all the moves, so reactivate analysis mode */
14855         SendToProgram("analyze\n", &first);
14856         first.analyzing = TRUE;
14857         /*first.maybeThinking = TRUE;*/
14858         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14859     }
14860 }
14861
14862 void
14863 BackwardInner (int target)
14864 {
14865     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14866
14867     if (appData.debugMode)
14868         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14869                 target, currentMove, forwardMostMove);
14870
14871     if (gameMode == EditPosition) return;
14872     seekGraphUp = FALSE;
14873     MarkTargetSquares(1);
14874     if (currentMove <= backwardMostMove) {
14875         ClearHighlights();
14876         DrawPosition(full_redraw, boards[currentMove]);
14877         return;
14878     }
14879     if (gameMode == PlayFromGameFile && !pausing)
14880       PauseEvent();
14881
14882     if (moveList[target][0]) {
14883         int fromX, fromY, toX, toY;
14884         toX = moveList[target][2] - AAA;
14885         toY = moveList[target][3] - ONE;
14886         if (moveList[target][1] == '@') {
14887             if (appData.highlightLastMove) {
14888                 SetHighlights(-1, -1, toX, toY);
14889             }
14890         } else {
14891             fromX = moveList[target][0] - AAA;
14892             fromY = moveList[target][1] - ONE;
14893             if (target == currentMove - 1) {
14894                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14895             }
14896             if (appData.highlightLastMove) {
14897                 SetHighlights(fromX, fromY, toX, toY);
14898             }
14899         }
14900     }
14901     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14902         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14903         while (currentMove > target) {
14904             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14905                 // null move cannot be undone. Reload program with move history before it.
14906                 int i;
14907                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14908                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14909                 }
14910                 SendBoard(&first, i);
14911               if(second.analyzing) SendBoard(&second, i);
14912                 for(currentMove=i; currentMove<target; currentMove++) {
14913                     SendMoveToProgram(currentMove, &first);
14914                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14915                 }
14916                 break;
14917             }
14918             SendToBoth("undo\n");
14919             currentMove--;
14920         }
14921     } else {
14922         currentMove = target;
14923     }
14924
14925     if (gameMode == EditGame || gameMode == EndOfGame) {
14926         whiteTimeRemaining = timeRemaining[0][currentMove];
14927         blackTimeRemaining = timeRemaining[1][currentMove];
14928     }
14929     DisplayBothClocks();
14930     DisplayMove(currentMove - 1);
14931     DrawPosition(full_redraw, boards[currentMove]);
14932     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14933     // [HGM] PV info: routine tests if comment empty
14934     DisplayComment(currentMove - 1, commentList[currentMove]);
14935     ClearMap(); // [HGM] exclude: invalidate map
14936 }
14937
14938 void
14939 BackwardEvent ()
14940 {
14941     if (gameMode == IcsExamining && !pausing) {
14942         SendToICS(ics_prefix);
14943         SendToICS("backward\n");
14944     } else {
14945         BackwardInner(currentMove - 1);
14946     }
14947 }
14948
14949 void
14950 ToStartEvent ()
14951 {
14952     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14953         /* to optimize, we temporarily turn off analysis mode while we undo
14954          * all the moves. Otherwise we get analysis output after each undo.
14955          */
14956         if (first.analysisSupport) {
14957           SendToProgram("exit\nforce\n", &first);
14958           first.analyzing = FALSE;
14959         }
14960     }
14961
14962     if (gameMode == IcsExamining && !pausing) {
14963         SendToICS(ics_prefix);
14964         SendToICS("backward 999999\n");
14965     } else {
14966         BackwardInner(backwardMostMove);
14967     }
14968
14969     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14970         /* we have fed all the moves, so reactivate analysis mode */
14971         SendToProgram("analyze\n", &first);
14972         first.analyzing = TRUE;
14973         /*first.maybeThinking = TRUE;*/
14974         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14975     }
14976 }
14977
14978 void
14979 ToNrEvent (int to)
14980 {
14981   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14982   if (to >= forwardMostMove) to = forwardMostMove;
14983   if (to <= backwardMostMove) to = backwardMostMove;
14984   if (to < currentMove) {
14985     BackwardInner(to);
14986   } else {
14987     ForwardInner(to);
14988   }
14989 }
14990
14991 void
14992 RevertEvent (Boolean annotate)
14993 {
14994     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14995         return;
14996     }
14997     if (gameMode != IcsExamining) {
14998         DisplayError(_("You are not examining a game"), 0);
14999         return;
15000     }
15001     if (pausing) {
15002         DisplayError(_("You can't revert while pausing"), 0);
15003         return;
15004     }
15005     SendToICS(ics_prefix);
15006     SendToICS("revert\n");
15007 }
15008
15009 void
15010 RetractMoveEvent ()
15011 {
15012     switch (gameMode) {
15013       case MachinePlaysWhite:
15014       case MachinePlaysBlack:
15015         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15016             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15017             return;
15018         }
15019         if (forwardMostMove < 2) return;
15020         currentMove = forwardMostMove = forwardMostMove - 2;
15021         whiteTimeRemaining = timeRemaining[0][currentMove];
15022         blackTimeRemaining = timeRemaining[1][currentMove];
15023         DisplayBothClocks();
15024         DisplayMove(currentMove - 1);
15025         ClearHighlights();/*!! could figure this out*/
15026         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15027         SendToProgram("remove\n", &first);
15028         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15029         break;
15030
15031       case BeginningOfGame:
15032       default:
15033         break;
15034
15035       case IcsPlayingWhite:
15036       case IcsPlayingBlack:
15037         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15038             SendToICS(ics_prefix);
15039             SendToICS("takeback 2\n");
15040         } else {
15041             SendToICS(ics_prefix);
15042             SendToICS("takeback 1\n");
15043         }
15044         break;
15045     }
15046 }
15047
15048 void
15049 MoveNowEvent ()
15050 {
15051     ChessProgramState *cps;
15052
15053     switch (gameMode) {
15054       case MachinePlaysWhite:
15055         if (!WhiteOnMove(forwardMostMove)) {
15056             DisplayError(_("It is your turn"), 0);
15057             return;
15058         }
15059         cps = &first;
15060         break;
15061       case MachinePlaysBlack:
15062         if (WhiteOnMove(forwardMostMove)) {
15063             DisplayError(_("It is your turn"), 0);
15064             return;
15065         }
15066         cps = &first;
15067         break;
15068       case TwoMachinesPlay:
15069         if (WhiteOnMove(forwardMostMove) ==
15070             (first.twoMachinesColor[0] == 'w')) {
15071             cps = &first;
15072         } else {
15073             cps = &second;
15074         }
15075         break;
15076       case BeginningOfGame:
15077       default:
15078         return;
15079     }
15080     SendToProgram("?\n", cps);
15081 }
15082
15083 void
15084 TruncateGameEvent ()
15085 {
15086     EditGameEvent();
15087     if (gameMode != EditGame) return;
15088     TruncateGame();
15089 }
15090
15091 void
15092 TruncateGame ()
15093 {
15094     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15095     if (forwardMostMove > currentMove) {
15096         if (gameInfo.resultDetails != NULL) {
15097             free(gameInfo.resultDetails);
15098             gameInfo.resultDetails = NULL;
15099             gameInfo.result = GameUnfinished;
15100         }
15101         forwardMostMove = currentMove;
15102         HistorySet(parseList, backwardMostMove, forwardMostMove,
15103                    currentMove-1);
15104     }
15105 }
15106
15107 void
15108 HintEvent ()
15109 {
15110     if (appData.noChessProgram) return;
15111     switch (gameMode) {
15112       case MachinePlaysWhite:
15113         if (WhiteOnMove(forwardMostMove)) {
15114             DisplayError(_("Wait until your turn"), 0);
15115             return;
15116         }
15117         break;
15118       case BeginningOfGame:
15119       case MachinePlaysBlack:
15120         if (!WhiteOnMove(forwardMostMove)) {
15121             DisplayError(_("Wait until your turn"), 0);
15122             return;
15123         }
15124         break;
15125       default:
15126         DisplayError(_("No hint available"), 0);
15127         return;
15128     }
15129     SendToProgram("hint\n", &first);
15130     hintRequested = TRUE;
15131 }
15132
15133 void
15134 CreateBookEvent ()
15135 {
15136     ListGame * lg = (ListGame *) gameList.head;
15137     FILE *f;
15138     int nItem;
15139     static int secondTime = FALSE;
15140
15141     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15142         DisplayError(_("Game list not loaded or empty"), 0);
15143         return;
15144     }
15145
15146     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15147         fclose(f);
15148         secondTime++;
15149         DisplayNote(_("Book file exists! Try again for overwrite."));
15150         return;
15151     }
15152
15153     creatingBook = TRUE;
15154     secondTime = FALSE;
15155
15156     /* Get list size */
15157     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15158         LoadGame(f, nItem, "", TRUE);
15159         AddGameToBook(TRUE);
15160         lg = (ListGame *) lg->node.succ;
15161     }
15162
15163     creatingBook = FALSE;
15164     FlushBook();
15165 }
15166
15167 void
15168 BookEvent ()
15169 {
15170     if (appData.noChessProgram) return;
15171     switch (gameMode) {
15172       case MachinePlaysWhite:
15173         if (WhiteOnMove(forwardMostMove)) {
15174             DisplayError(_("Wait until your turn"), 0);
15175             return;
15176         }
15177         break;
15178       case BeginningOfGame:
15179       case MachinePlaysBlack:
15180         if (!WhiteOnMove(forwardMostMove)) {
15181             DisplayError(_("Wait until your turn"), 0);
15182             return;
15183         }
15184         break;
15185       case EditPosition:
15186         EditPositionDone(TRUE);
15187         break;
15188       case TwoMachinesPlay:
15189         return;
15190       default:
15191         break;
15192     }
15193     SendToProgram("bk\n", &first);
15194     bookOutput[0] = NULLCHAR;
15195     bookRequested = TRUE;
15196 }
15197
15198 void
15199 AboutGameEvent ()
15200 {
15201     char *tags = PGNTags(&gameInfo);
15202     TagsPopUp(tags, CmailMsg());
15203     free(tags);
15204 }
15205
15206 /* end button procedures */
15207
15208 void
15209 PrintPosition (FILE *fp, int move)
15210 {
15211     int i, j;
15212
15213     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15214         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15215             char c = PieceToChar(boards[move][i][j]);
15216             fputc(c == 'x' ? '.' : c, fp);
15217             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15218         }
15219     }
15220     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15221       fprintf(fp, "white to play\n");
15222     else
15223       fprintf(fp, "black to play\n");
15224 }
15225
15226 void
15227 PrintOpponents (FILE *fp)
15228 {
15229     if (gameInfo.white != NULL) {
15230         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15231     } else {
15232         fprintf(fp, "\n");
15233     }
15234 }
15235
15236 /* Find last component of program's own name, using some heuristics */
15237 void
15238 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15239 {
15240     char *p, *q, c;
15241     int local = (strcmp(host, "localhost") == 0);
15242     while (!local && (p = strchr(prog, ';')) != NULL) {
15243         p++;
15244         while (*p == ' ') p++;
15245         prog = p;
15246     }
15247     if (*prog == '"' || *prog == '\'') {
15248         q = strchr(prog + 1, *prog);
15249     } else {
15250         q = strchr(prog, ' ');
15251     }
15252     if (q == NULL) q = prog + strlen(prog);
15253     p = q;
15254     while (p >= prog && *p != '/' && *p != '\\') p--;
15255     p++;
15256     if(p == prog && *p == '"') p++;
15257     c = *q; *q = 0;
15258     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15259     memcpy(buf, p, q - p);
15260     buf[q - p] = NULLCHAR;
15261     if (!local) {
15262         strcat(buf, "@");
15263         strcat(buf, host);
15264     }
15265 }
15266
15267 char *
15268 TimeControlTagValue ()
15269 {
15270     char buf[MSG_SIZ];
15271     if (!appData.clockMode) {
15272       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15273     } else if (movesPerSession > 0) {
15274       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15275     } else if (timeIncrement == 0) {
15276       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15277     } else {
15278       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15279     }
15280     return StrSave(buf);
15281 }
15282
15283 void
15284 SetGameInfo ()
15285 {
15286     /* This routine is used only for certain modes */
15287     VariantClass v = gameInfo.variant;
15288     ChessMove r = GameUnfinished;
15289     char *p = NULL;
15290
15291     if(keepInfo) return;
15292
15293     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15294         r = gameInfo.result;
15295         p = gameInfo.resultDetails;
15296         gameInfo.resultDetails = NULL;
15297     }
15298     ClearGameInfo(&gameInfo);
15299     gameInfo.variant = v;
15300
15301     switch (gameMode) {
15302       case MachinePlaysWhite:
15303         gameInfo.event = StrSave( appData.pgnEventHeader );
15304         gameInfo.site = StrSave(HostName());
15305         gameInfo.date = PGNDate();
15306         gameInfo.round = StrSave("-");
15307         gameInfo.white = StrSave(first.tidy);
15308         gameInfo.black = StrSave(UserName());
15309         gameInfo.timeControl = TimeControlTagValue();
15310         break;
15311
15312       case MachinePlaysBlack:
15313         gameInfo.event = StrSave( appData.pgnEventHeader );
15314         gameInfo.site = StrSave(HostName());
15315         gameInfo.date = PGNDate();
15316         gameInfo.round = StrSave("-");
15317         gameInfo.white = StrSave(UserName());
15318         gameInfo.black = StrSave(first.tidy);
15319         gameInfo.timeControl = TimeControlTagValue();
15320         break;
15321
15322       case TwoMachinesPlay:
15323         gameInfo.event = StrSave( appData.pgnEventHeader );
15324         gameInfo.site = StrSave(HostName());
15325         gameInfo.date = PGNDate();
15326         if (roundNr > 0) {
15327             char buf[MSG_SIZ];
15328             snprintf(buf, MSG_SIZ, "%d", roundNr);
15329             gameInfo.round = StrSave(buf);
15330         } else {
15331             gameInfo.round = StrSave("-");
15332         }
15333         if (first.twoMachinesColor[0] == 'w') {
15334             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15335             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15336         } else {
15337             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15338             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15339         }
15340         gameInfo.timeControl = TimeControlTagValue();
15341         break;
15342
15343       case EditGame:
15344         gameInfo.event = StrSave("Edited game");
15345         gameInfo.site = StrSave(HostName());
15346         gameInfo.date = PGNDate();
15347         gameInfo.round = StrSave("-");
15348         gameInfo.white = StrSave("-");
15349         gameInfo.black = StrSave("-");
15350         gameInfo.result = r;
15351         gameInfo.resultDetails = p;
15352         break;
15353
15354       case EditPosition:
15355         gameInfo.event = StrSave("Edited position");
15356         gameInfo.site = StrSave(HostName());
15357         gameInfo.date = PGNDate();
15358         gameInfo.round = StrSave("-");
15359         gameInfo.white = StrSave("-");
15360         gameInfo.black = StrSave("-");
15361         break;
15362
15363       case IcsPlayingWhite:
15364       case IcsPlayingBlack:
15365       case IcsObserving:
15366       case IcsExamining:
15367         break;
15368
15369       case PlayFromGameFile:
15370         gameInfo.event = StrSave("Game from non-PGN file");
15371         gameInfo.site = StrSave(HostName());
15372         gameInfo.date = PGNDate();
15373         gameInfo.round = StrSave("-");
15374         gameInfo.white = StrSave("?");
15375         gameInfo.black = StrSave("?");
15376         break;
15377
15378       default:
15379         break;
15380     }
15381 }
15382
15383 void
15384 ReplaceComment (int index, char *text)
15385 {
15386     int len;
15387     char *p;
15388     float score;
15389
15390     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15391        pvInfoList[index-1].depth == len &&
15392        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15393        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15394     while (*text == '\n') text++;
15395     len = strlen(text);
15396     while (len > 0 && text[len - 1] == '\n') len--;
15397
15398     if (commentList[index] != NULL)
15399       free(commentList[index]);
15400
15401     if (len == 0) {
15402         commentList[index] = NULL;
15403         return;
15404     }
15405   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15406       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15407       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15408     commentList[index] = (char *) malloc(len + 2);
15409     strncpy(commentList[index], text, len);
15410     commentList[index][len] = '\n';
15411     commentList[index][len + 1] = NULLCHAR;
15412   } else {
15413     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15414     char *p;
15415     commentList[index] = (char *) malloc(len + 7);
15416     safeStrCpy(commentList[index], "{\n", 3);
15417     safeStrCpy(commentList[index]+2, text, len+1);
15418     commentList[index][len+2] = NULLCHAR;
15419     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15420     strcat(commentList[index], "\n}\n");
15421   }
15422 }
15423
15424 void
15425 CrushCRs (char *text)
15426 {
15427   char *p = text;
15428   char *q = text;
15429   char ch;
15430
15431   do {
15432     ch = *p++;
15433     if (ch == '\r') continue;
15434     *q++ = ch;
15435   } while (ch != '\0');
15436 }
15437
15438 void
15439 AppendComment (int index, char *text, Boolean addBraces)
15440 /* addBraces  tells if we should add {} */
15441 {
15442     int oldlen, len;
15443     char *old;
15444
15445 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15446     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15447
15448     CrushCRs(text);
15449     while (*text == '\n') text++;
15450     len = strlen(text);
15451     while (len > 0 && text[len - 1] == '\n') len--;
15452     text[len] = NULLCHAR;
15453
15454     if (len == 0) return;
15455
15456     if (commentList[index] != NULL) {
15457       Boolean addClosingBrace = addBraces;
15458         old = commentList[index];
15459         oldlen = strlen(old);
15460         while(commentList[index][oldlen-1] ==  '\n')
15461           commentList[index][--oldlen] = NULLCHAR;
15462         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15463         safeStrCpy(commentList[index], old, oldlen + len + 6);
15464         free(old);
15465         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15466         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15467           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15468           while (*text == '\n') { text++; len--; }
15469           commentList[index][--oldlen] = NULLCHAR;
15470       }
15471         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15472         else          strcat(commentList[index], "\n");
15473         strcat(commentList[index], text);
15474         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15475         else          strcat(commentList[index], "\n");
15476     } else {
15477         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15478         if(addBraces)
15479           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15480         else commentList[index][0] = NULLCHAR;
15481         strcat(commentList[index], text);
15482         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15483         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15484     }
15485 }
15486
15487 static char *
15488 FindStr (char * text, char * sub_text)
15489 {
15490     char * result = strstr( text, sub_text );
15491
15492     if( result != NULL ) {
15493         result += strlen( sub_text );
15494     }
15495
15496     return result;
15497 }
15498
15499 /* [AS] Try to extract PV info from PGN comment */
15500 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15501 char *
15502 GetInfoFromComment (int index, char * text)
15503 {
15504     char * sep = text, *p;
15505
15506     if( text != NULL && index > 0 ) {
15507         int score = 0;
15508         int depth = 0;
15509         int time = -1, sec = 0, deci;
15510         char * s_eval = FindStr( text, "[%eval " );
15511         char * s_emt = FindStr( text, "[%emt " );
15512
15513         if( s_eval != NULL || s_emt != NULL ) {
15514             /* New style */
15515             char delim;
15516
15517             if( s_eval != NULL ) {
15518                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15519                     return text;
15520                 }
15521
15522                 if( delim != ']' ) {
15523                     return text;
15524                 }
15525             }
15526
15527             if( s_emt != NULL ) {
15528             }
15529                 return text;
15530         }
15531         else {
15532             /* We expect something like: [+|-]nnn.nn/dd */
15533             int score_lo = 0;
15534
15535             if(*text != '{') return text; // [HGM] braces: must be normal comment
15536
15537             sep = strchr( text, '/' );
15538             if( sep == NULL || sep < (text+4) ) {
15539                 return text;
15540             }
15541
15542             p = text;
15543             if(p[1] == '(') { // comment starts with PV
15544                p = strchr(p, ')'); // locate end of PV
15545                if(p == NULL || sep < p+5) return text;
15546                // at this point we have something like "{(.*) +0.23/6 ..."
15547                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15548                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15549                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15550             }
15551             time = -1; sec = -1; deci = -1;
15552             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15553                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15554                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15555                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15556                 return text;
15557             }
15558
15559             if( score_lo < 0 || score_lo >= 100 ) {
15560                 return text;
15561             }
15562
15563             if(sec >= 0) time = 600*time + 10*sec; else
15564             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15565
15566             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15567
15568             /* [HGM] PV time: now locate end of PV info */
15569             while( *++sep >= '0' && *sep <= '9'); // strip depth
15570             if(time >= 0)
15571             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15572             if(sec >= 0)
15573             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15574             if(deci >= 0)
15575             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15576             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15577         }
15578
15579         if( depth <= 0 ) {
15580             return text;
15581         }
15582
15583         if( time < 0 ) {
15584             time = -1;
15585         }
15586
15587         pvInfoList[index-1].depth = depth;
15588         pvInfoList[index-1].score = score;
15589         pvInfoList[index-1].time  = 10*time; // centi-sec
15590         if(*sep == '}') *sep = 0; else *--sep = '{';
15591         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15592     }
15593     return sep;
15594 }
15595
15596 void
15597 SendToProgram (char *message, ChessProgramState *cps)
15598 {
15599     int count, outCount, error;
15600     char buf[MSG_SIZ];
15601
15602     if (cps->pr == NoProc) return;
15603     Attention(cps);
15604
15605     if (appData.debugMode) {
15606         TimeMark now;
15607         GetTimeMark(&now);
15608         fprintf(debugFP, "%ld >%-6s: %s",
15609                 SubtractTimeMarks(&now, &programStartTime),
15610                 cps->which, message);
15611         if(serverFP)
15612             fprintf(serverFP, "%ld >%-6s: %s",
15613                 SubtractTimeMarks(&now, &programStartTime),
15614                 cps->which, message), fflush(serverFP);
15615     }
15616
15617     count = strlen(message);
15618     outCount = OutputToProcess(cps->pr, message, count, &error);
15619     if (outCount < count && !exiting
15620                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15621       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15622       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15623         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15624             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15625                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15626                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15627                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15628             } else {
15629                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15630                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15631                 gameInfo.result = res;
15632             }
15633             gameInfo.resultDetails = StrSave(buf);
15634         }
15635         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15636         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15637     }
15638 }
15639
15640 void
15641 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15642 {
15643     char *end_str;
15644     char buf[MSG_SIZ];
15645     ChessProgramState *cps = (ChessProgramState *)closure;
15646
15647     if (isr != cps->isr) return; /* Killed intentionally */
15648     if (count <= 0) {
15649         if (count == 0) {
15650             RemoveInputSource(cps->isr);
15651             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15652                     _(cps->which), cps->program);
15653             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15654             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15655                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15656                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15657                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15658                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15659                 } else {
15660                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15661                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15662                     gameInfo.result = res;
15663                 }
15664                 gameInfo.resultDetails = StrSave(buf);
15665             }
15666             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15667             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15668         } else {
15669             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15670                     _(cps->which), cps->program);
15671             RemoveInputSource(cps->isr);
15672
15673             /* [AS] Program is misbehaving badly... kill it */
15674             if( count == -2 ) {
15675                 DestroyChildProcess( cps->pr, 9 );
15676                 cps->pr = NoProc;
15677             }
15678
15679             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15680         }
15681         return;
15682     }
15683
15684     if ((end_str = strchr(message, '\r')) != NULL)
15685       *end_str = NULLCHAR;
15686     if ((end_str = strchr(message, '\n')) != NULL)
15687       *end_str = NULLCHAR;
15688
15689     if (appData.debugMode) {
15690         TimeMark now; int print = 1;
15691         char *quote = ""; char c; int i;
15692
15693         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15694                 char start = message[0];
15695                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15696                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15697                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15698                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15699                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15700                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15701                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15702                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15703                    sscanf(message, "hint: %c", &c)!=1 &&
15704                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15705                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15706                     print = (appData.engineComments >= 2);
15707                 }
15708                 message[0] = start; // restore original message
15709         }
15710         if(print) {
15711                 GetTimeMark(&now);
15712                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15713                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15714                         quote,
15715                         message);
15716                 if(serverFP)
15717                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15718                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15719                         quote,
15720                         message), fflush(serverFP);
15721         }
15722     }
15723
15724     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15725     if (appData.icsEngineAnalyze) {
15726         if (strstr(message, "whisper") != NULL ||
15727              strstr(message, "kibitz") != NULL ||
15728             strstr(message, "tellics") != NULL) return;
15729     }
15730
15731     HandleMachineMove(message, cps);
15732 }
15733
15734
15735 void
15736 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15737 {
15738     char buf[MSG_SIZ];
15739     int seconds;
15740
15741     if( timeControl_2 > 0 ) {
15742         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15743             tc = timeControl_2;
15744         }
15745     }
15746     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15747     inc /= cps->timeOdds;
15748     st  /= cps->timeOdds;
15749
15750     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15751
15752     if (st > 0) {
15753       /* Set exact time per move, normally using st command */
15754       if (cps->stKludge) {
15755         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15756         seconds = st % 60;
15757         if (seconds == 0) {
15758           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15759         } else {
15760           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15761         }
15762       } else {
15763         snprintf(buf, MSG_SIZ, "st %d\n", st);
15764       }
15765     } else {
15766       /* Set conventional or incremental time control, using level command */
15767       if (seconds == 0) {
15768         /* Note old gnuchess bug -- minutes:seconds used to not work.
15769            Fixed in later versions, but still avoid :seconds
15770            when seconds is 0. */
15771         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15772       } else {
15773         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15774                  seconds, inc/1000.);
15775       }
15776     }
15777     SendToProgram(buf, cps);
15778
15779     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15780     /* Orthogonally, limit search to given depth */
15781     if (sd > 0) {
15782       if (cps->sdKludge) {
15783         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15784       } else {
15785         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15786       }
15787       SendToProgram(buf, cps);
15788     }
15789
15790     if(cps->nps >= 0) { /* [HGM] nps */
15791         if(cps->supportsNPS == FALSE)
15792           cps->nps = -1; // don't use if engine explicitly says not supported!
15793         else {
15794           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15795           SendToProgram(buf, cps);
15796         }
15797     }
15798 }
15799
15800 ChessProgramState *
15801 WhitePlayer ()
15802 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15803 {
15804     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15805        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15806         return &second;
15807     return &first;
15808 }
15809
15810 void
15811 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15812 {
15813     char message[MSG_SIZ];
15814     long time, otime;
15815
15816     /* Note: this routine must be called when the clocks are stopped
15817        or when they have *just* been set or switched; otherwise
15818        it will be off by the time since the current tick started.
15819     */
15820     if (machineWhite) {
15821         time = whiteTimeRemaining / 10;
15822         otime = blackTimeRemaining / 10;
15823     } else {
15824         time = blackTimeRemaining / 10;
15825         otime = whiteTimeRemaining / 10;
15826     }
15827     /* [HGM] translate opponent's time by time-odds factor */
15828     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15829
15830     if (time <= 0) time = 1;
15831     if (otime <= 0) otime = 1;
15832
15833     snprintf(message, MSG_SIZ, "time %ld\n", time);
15834     SendToProgram(message, cps);
15835
15836     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15837     SendToProgram(message, cps);
15838 }
15839
15840 int
15841 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15842 {
15843   char buf[MSG_SIZ];
15844   int len = strlen(name);
15845   int val;
15846
15847   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15848     (*p) += len + 1;
15849     sscanf(*p, "%d", &val);
15850     *loc = (val != 0);
15851     while (**p && **p != ' ')
15852       (*p)++;
15853     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15854     SendToProgram(buf, cps);
15855     return TRUE;
15856   }
15857   return FALSE;
15858 }
15859
15860 int
15861 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15862 {
15863   char buf[MSG_SIZ];
15864   int len = strlen(name);
15865   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15866     (*p) += len + 1;
15867     sscanf(*p, "%d", loc);
15868     while (**p && **p != ' ') (*p)++;
15869     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15870     SendToProgram(buf, cps);
15871     return TRUE;
15872   }
15873   return FALSE;
15874 }
15875
15876 int
15877 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15878 {
15879   char buf[MSG_SIZ];
15880   int len = strlen(name);
15881   if (strncmp((*p), name, len) == 0
15882       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15883     (*p) += len + 2;
15884     sscanf(*p, "%[^\"]", loc);
15885     while (**p && **p != '\"') (*p)++;
15886     if (**p == '\"') (*p)++;
15887     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15888     SendToProgram(buf, cps);
15889     return TRUE;
15890   }
15891   return FALSE;
15892 }
15893
15894 int
15895 ParseOption (Option *opt, ChessProgramState *cps)
15896 // [HGM] options: process the string that defines an engine option, and determine
15897 // name, type, default value, and allowed value range
15898 {
15899         char *p, *q, buf[MSG_SIZ];
15900         int n, min = (-1)<<31, max = 1<<31, def;
15901
15902         if(p = strstr(opt->name, " -spin ")) {
15903             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15904             if(max < min) max = min; // enforce consistency
15905             if(def < min) def = min;
15906             if(def > max) def = max;
15907             opt->value = def;
15908             opt->min = min;
15909             opt->max = max;
15910             opt->type = Spin;
15911         } else if((p = strstr(opt->name, " -slider "))) {
15912             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15913             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15914             if(max < min) max = min; // enforce consistency
15915             if(def < min) def = min;
15916             if(def > max) def = max;
15917             opt->value = def;
15918             opt->min = min;
15919             opt->max = max;
15920             opt->type = Spin; // Slider;
15921         } else if((p = strstr(opt->name, " -string "))) {
15922             opt->textValue = p+9;
15923             opt->type = TextBox;
15924         } else if((p = strstr(opt->name, " -file "))) {
15925             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15926             opt->textValue = p+7;
15927             opt->type = FileName; // FileName;
15928         } else if((p = strstr(opt->name, " -path "))) {
15929             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15930             opt->textValue = p+7;
15931             opt->type = PathName; // PathName;
15932         } else if(p = strstr(opt->name, " -check ")) {
15933             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15934             opt->value = (def != 0);
15935             opt->type = CheckBox;
15936         } else if(p = strstr(opt->name, " -combo ")) {
15937             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15938             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15939             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15940             opt->value = n = 0;
15941             while(q = StrStr(q, " /// ")) {
15942                 n++; *q = 0;    // count choices, and null-terminate each of them
15943                 q += 5;
15944                 if(*q == '*') { // remember default, which is marked with * prefix
15945                     q++;
15946                     opt->value = n;
15947                 }
15948                 cps->comboList[cps->comboCnt++] = q;
15949             }
15950             cps->comboList[cps->comboCnt++] = NULL;
15951             opt->max = n + 1;
15952             opt->type = ComboBox;
15953         } else if(p = strstr(opt->name, " -button")) {
15954             opt->type = Button;
15955         } else if(p = strstr(opt->name, " -save")) {
15956             opt->type = SaveButton;
15957         } else return FALSE;
15958         *p = 0; // terminate option name
15959         // now look if the command-line options define a setting for this engine option.
15960         if(cps->optionSettings && cps->optionSettings[0])
15961             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15962         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15963           snprintf(buf, MSG_SIZ, "option %s", p);
15964                 if(p = strstr(buf, ",")) *p = 0;
15965                 if(q = strchr(buf, '=')) switch(opt->type) {
15966                     case ComboBox:
15967                         for(n=0; n<opt->max; n++)
15968                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15969                         break;
15970                     case TextBox:
15971                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15972                         break;
15973                     case Spin:
15974                     case CheckBox:
15975                         opt->value = atoi(q+1);
15976                     default:
15977                         break;
15978                 }
15979                 strcat(buf, "\n");
15980                 SendToProgram(buf, cps);
15981         }
15982         return TRUE;
15983 }
15984
15985 void
15986 FeatureDone (ChessProgramState *cps, int val)
15987 {
15988   DelayedEventCallback cb = GetDelayedEvent();
15989   if ((cb == InitBackEnd3 && cps == &first) ||
15990       (cb == SettingsMenuIfReady && cps == &second) ||
15991       (cb == LoadEngine) ||
15992       (cb == TwoMachinesEventIfReady)) {
15993     CancelDelayedEvent();
15994     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15995   }
15996   cps->initDone = val;
15997 }
15998
15999 /* Parse feature command from engine */
16000 void
16001 ParseFeatures (char *args, ChessProgramState *cps)
16002 {
16003   char *p = args;
16004   char *q;
16005   int val;
16006   char buf[MSG_SIZ];
16007
16008   for (;;) {
16009     while (*p == ' ') p++;
16010     if (*p == NULLCHAR) return;
16011
16012     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16013     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16014     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16015     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16016     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16017     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16018     if (BoolFeature(&p, "reuse", &val, cps)) {
16019       /* Engine can disable reuse, but can't enable it if user said no */
16020       if (!val) cps->reuse = FALSE;
16021       continue;
16022     }
16023     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16024     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16025       if (gameMode == TwoMachinesPlay) {
16026         DisplayTwoMachinesTitle();
16027       } else {
16028         DisplayTitle("");
16029       }
16030       continue;
16031     }
16032     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16033     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16034     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16035     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16036     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16037     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16038     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16039     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16040     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16041     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16042     if (IntFeature(&p, "done", &val, cps)) {
16043       FeatureDone(cps, val);
16044       continue;
16045     }
16046     /* Added by Tord: */
16047     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16048     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16049     /* End of additions by Tord */
16050
16051     /* [HGM] added features: */
16052     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16053     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16054     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16055     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16056     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16057     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16058     if (StringFeature(&p, "option", buf, cps)) {
16059         FREE(cps->option[cps->nrOptions].name);
16060         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16061         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16062         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16063           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16064             SendToProgram(buf, cps);
16065             continue;
16066         }
16067         if(cps->nrOptions >= MAX_OPTIONS) {
16068             cps->nrOptions--;
16069             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16070             DisplayError(buf, 0);
16071         }
16072         continue;
16073     }
16074     /* End of additions by HGM */
16075
16076     /* unknown feature: complain and skip */
16077     q = p;
16078     while (*q && *q != '=') q++;
16079     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16080     SendToProgram(buf, cps);
16081     p = q;
16082     if (*p == '=') {
16083       p++;
16084       if (*p == '\"') {
16085         p++;
16086         while (*p && *p != '\"') p++;
16087         if (*p == '\"') p++;
16088       } else {
16089         while (*p && *p != ' ') p++;
16090       }
16091     }
16092   }
16093
16094 }
16095
16096 void
16097 PeriodicUpdatesEvent (int newState)
16098 {
16099     if (newState == appData.periodicUpdates)
16100       return;
16101
16102     appData.periodicUpdates=newState;
16103
16104     /* Display type changes, so update it now */
16105 //    DisplayAnalysis();
16106
16107     /* Get the ball rolling again... */
16108     if (newState) {
16109         AnalysisPeriodicEvent(1);
16110         StartAnalysisClock();
16111     }
16112 }
16113
16114 void
16115 PonderNextMoveEvent (int newState)
16116 {
16117     if (newState == appData.ponderNextMove) return;
16118     if (gameMode == EditPosition) EditPositionDone(TRUE);
16119     if (newState) {
16120         SendToProgram("hard\n", &first);
16121         if (gameMode == TwoMachinesPlay) {
16122             SendToProgram("hard\n", &second);
16123         }
16124     } else {
16125         SendToProgram("easy\n", &first);
16126         thinkOutput[0] = NULLCHAR;
16127         if (gameMode == TwoMachinesPlay) {
16128             SendToProgram("easy\n", &second);
16129         }
16130     }
16131     appData.ponderNextMove = newState;
16132 }
16133
16134 void
16135 NewSettingEvent (int option, int *feature, char *command, int value)
16136 {
16137     char buf[MSG_SIZ];
16138
16139     if (gameMode == EditPosition) EditPositionDone(TRUE);
16140     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16141     if(feature == NULL || *feature) SendToProgram(buf, &first);
16142     if (gameMode == TwoMachinesPlay) {
16143         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16144     }
16145 }
16146
16147 void
16148 ShowThinkingEvent ()
16149 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16150 {
16151     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16152     int newState = appData.showThinking
16153         // [HGM] thinking: other features now need thinking output as well
16154         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16155
16156     if (oldState == newState) return;
16157     oldState = newState;
16158     if (gameMode == EditPosition) EditPositionDone(TRUE);
16159     if (oldState) {
16160         SendToProgram("post\n", &first);
16161         if (gameMode == TwoMachinesPlay) {
16162             SendToProgram("post\n", &second);
16163         }
16164     } else {
16165         SendToProgram("nopost\n", &first);
16166         thinkOutput[0] = NULLCHAR;
16167         if (gameMode == TwoMachinesPlay) {
16168             SendToProgram("nopost\n", &second);
16169         }
16170     }
16171 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16172 }
16173
16174 void
16175 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16176 {
16177   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16178   if (pr == NoProc) return;
16179   AskQuestion(title, question, replyPrefix, pr);
16180 }
16181
16182 void
16183 TypeInEvent (char firstChar)
16184 {
16185     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16186         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16187         gameMode == AnalyzeMode || gameMode == EditGame ||
16188         gameMode == EditPosition || gameMode == IcsExamining ||
16189         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16190         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16191                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16192                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16193         gameMode == Training) PopUpMoveDialog(firstChar);
16194 }
16195
16196 void
16197 TypeInDoneEvent (char *move)
16198 {
16199         Board board;
16200         int n, fromX, fromY, toX, toY;
16201         char promoChar;
16202         ChessMove moveType;
16203
16204         // [HGM] FENedit
16205         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16206                 EditPositionPasteFEN(move);
16207                 return;
16208         }
16209         // [HGM] movenum: allow move number to be typed in any mode
16210         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16211           ToNrEvent(2*n-1);
16212           return;
16213         }
16214         // undocumented kludge: allow command-line option to be typed in!
16215         // (potentially fatal, and does not implement the effect of the option.)
16216         // should only be used for options that are values on which future decisions will be made,
16217         // and definitely not on options that would be used during initialization.
16218         if(strstr(move, "!!! -") == move) {
16219             ParseArgsFromString(move+4);
16220             return;
16221         }
16222
16223       if (gameMode != EditGame && currentMove != forwardMostMove &&
16224         gameMode != Training) {
16225         DisplayMoveError(_("Displayed move is not current"));
16226       } else {
16227         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16228           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16229         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16230         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16231           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16232           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16233         } else {
16234           DisplayMoveError(_("Could not parse move"));
16235         }
16236       }
16237 }
16238
16239 void
16240 DisplayMove (int moveNumber)
16241 {
16242     char message[MSG_SIZ];
16243     char res[MSG_SIZ];
16244     char cpThinkOutput[MSG_SIZ];
16245
16246     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16247
16248     if (moveNumber == forwardMostMove - 1 ||
16249         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16250
16251         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16252
16253         if (strchr(cpThinkOutput, '\n')) {
16254             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16255         }
16256     } else {
16257         *cpThinkOutput = NULLCHAR;
16258     }
16259
16260     /* [AS] Hide thinking from human user */
16261     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16262         *cpThinkOutput = NULLCHAR;
16263         if( thinkOutput[0] != NULLCHAR ) {
16264             int i;
16265
16266             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16267                 cpThinkOutput[i] = '.';
16268             }
16269             cpThinkOutput[i] = NULLCHAR;
16270             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16271         }
16272     }
16273
16274     if (moveNumber == forwardMostMove - 1 &&
16275         gameInfo.resultDetails != NULL) {
16276         if (gameInfo.resultDetails[0] == NULLCHAR) {
16277           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16278         } else {
16279           snprintf(res, MSG_SIZ, " {%s} %s",
16280                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16281         }
16282     } else {
16283         res[0] = NULLCHAR;
16284     }
16285
16286     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16287         DisplayMessage(res, cpThinkOutput);
16288     } else {
16289       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16290                 WhiteOnMove(moveNumber) ? " " : ".. ",
16291                 parseList[moveNumber], res);
16292         DisplayMessage(message, cpThinkOutput);
16293     }
16294 }
16295
16296 void
16297 DisplayComment (int moveNumber, char *text)
16298 {
16299     char title[MSG_SIZ];
16300
16301     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16302       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16303     } else {
16304       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16305               WhiteOnMove(moveNumber) ? " " : ".. ",
16306               parseList[moveNumber]);
16307     }
16308     if (text != NULL && (appData.autoDisplayComment || commentUp))
16309         CommentPopUp(title, text);
16310 }
16311
16312 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16313  * might be busy thinking or pondering.  It can be omitted if your
16314  * gnuchess is configured to stop thinking immediately on any user
16315  * input.  However, that gnuchess feature depends on the FIONREAD
16316  * ioctl, which does not work properly on some flavors of Unix.
16317  */
16318 void
16319 Attention (ChessProgramState *cps)
16320 {
16321 #if ATTENTION
16322     if (!cps->useSigint) return;
16323     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16324     switch (gameMode) {
16325       case MachinePlaysWhite:
16326       case MachinePlaysBlack:
16327       case TwoMachinesPlay:
16328       case IcsPlayingWhite:
16329       case IcsPlayingBlack:
16330       case AnalyzeMode:
16331       case AnalyzeFile:
16332         /* Skip if we know it isn't thinking */
16333         if (!cps->maybeThinking) return;
16334         if (appData.debugMode)
16335           fprintf(debugFP, "Interrupting %s\n", cps->which);
16336         InterruptChildProcess(cps->pr);
16337         cps->maybeThinking = FALSE;
16338         break;
16339       default:
16340         break;
16341     }
16342 #endif /*ATTENTION*/
16343 }
16344
16345 int
16346 CheckFlags ()
16347 {
16348     if (whiteTimeRemaining <= 0) {
16349         if (!whiteFlag) {
16350             whiteFlag = TRUE;
16351             if (appData.icsActive) {
16352                 if (appData.autoCallFlag &&
16353                     gameMode == IcsPlayingBlack && !blackFlag) {
16354                   SendToICS(ics_prefix);
16355                   SendToICS("flag\n");
16356                 }
16357             } else {
16358                 if (blackFlag) {
16359                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16360                 } else {
16361                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16362                     if (appData.autoCallFlag) {
16363                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16364                         return TRUE;
16365                     }
16366                 }
16367             }
16368         }
16369     }
16370     if (blackTimeRemaining <= 0) {
16371         if (!blackFlag) {
16372             blackFlag = TRUE;
16373             if (appData.icsActive) {
16374                 if (appData.autoCallFlag &&
16375                     gameMode == IcsPlayingWhite && !whiteFlag) {
16376                   SendToICS(ics_prefix);
16377                   SendToICS("flag\n");
16378                 }
16379             } else {
16380                 if (whiteFlag) {
16381                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16382                 } else {
16383                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16384                     if (appData.autoCallFlag) {
16385                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16386                         return TRUE;
16387                     }
16388                 }
16389             }
16390         }
16391     }
16392     return FALSE;
16393 }
16394
16395 void
16396 CheckTimeControl ()
16397 {
16398     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16399         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16400
16401     /*
16402      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16403      */
16404     if ( !WhiteOnMove(forwardMostMove) ) {
16405         /* White made time control */
16406         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16407         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16408         /* [HGM] time odds: correct new time quota for time odds! */
16409                                             / WhitePlayer()->timeOdds;
16410         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16411     } else {
16412         lastBlack -= blackTimeRemaining;
16413         /* Black made time control */
16414         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16415                                             / WhitePlayer()->other->timeOdds;
16416         lastWhite = whiteTimeRemaining;
16417     }
16418 }
16419
16420 void
16421 DisplayBothClocks ()
16422 {
16423     int wom = gameMode == EditPosition ?
16424       !blackPlaysFirst : WhiteOnMove(currentMove);
16425     DisplayWhiteClock(whiteTimeRemaining, wom);
16426     DisplayBlackClock(blackTimeRemaining, !wom);
16427 }
16428
16429
16430 /* Timekeeping seems to be a portability nightmare.  I think everyone
16431    has ftime(), but I'm really not sure, so I'm including some ifdefs
16432    to use other calls if you don't.  Clocks will be less accurate if
16433    you have neither ftime nor gettimeofday.
16434 */
16435
16436 /* VS 2008 requires the #include outside of the function */
16437 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16438 #include <sys/timeb.h>
16439 #endif
16440
16441 /* Get the current time as a TimeMark */
16442 void
16443 GetTimeMark (TimeMark *tm)
16444 {
16445 #if HAVE_GETTIMEOFDAY
16446
16447     struct timeval timeVal;
16448     struct timezone timeZone;
16449
16450     gettimeofday(&timeVal, &timeZone);
16451     tm->sec = (long) timeVal.tv_sec;
16452     tm->ms = (int) (timeVal.tv_usec / 1000L);
16453
16454 #else /*!HAVE_GETTIMEOFDAY*/
16455 #if HAVE_FTIME
16456
16457 // include <sys/timeb.h> / moved to just above start of function
16458     struct timeb timeB;
16459
16460     ftime(&timeB);
16461     tm->sec = (long) timeB.time;
16462     tm->ms = (int) timeB.millitm;
16463
16464 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16465     tm->sec = (long) time(NULL);
16466     tm->ms = 0;
16467 #endif
16468 #endif
16469 }
16470
16471 /* Return the difference in milliseconds between two
16472    time marks.  We assume the difference will fit in a long!
16473 */
16474 long
16475 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16476 {
16477     return 1000L*(tm2->sec - tm1->sec) +
16478            (long) (tm2->ms - tm1->ms);
16479 }
16480
16481
16482 /*
16483  * Code to manage the game clocks.
16484  *
16485  * In tournament play, black starts the clock and then white makes a move.
16486  * We give the human user a slight advantage if he is playing white---the
16487  * clocks don't run until he makes his first move, so it takes zero time.
16488  * Also, we don't account for network lag, so we could get out of sync
16489  * with GNU Chess's clock -- but then, referees are always right.
16490  */
16491
16492 static TimeMark tickStartTM;
16493 static long intendedTickLength;
16494
16495 long
16496 NextTickLength (long timeRemaining)
16497 {
16498     long nominalTickLength, nextTickLength;
16499
16500     if (timeRemaining > 0L && timeRemaining <= 10000L)
16501       nominalTickLength = 100L;
16502     else
16503       nominalTickLength = 1000L;
16504     nextTickLength = timeRemaining % nominalTickLength;
16505     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16506
16507     return nextTickLength;
16508 }
16509
16510 /* Adjust clock one minute up or down */
16511 void
16512 AdjustClock (Boolean which, int dir)
16513 {
16514     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16515     if(which) blackTimeRemaining += 60000*dir;
16516     else      whiteTimeRemaining += 60000*dir;
16517     DisplayBothClocks();
16518     adjustedClock = TRUE;
16519 }
16520
16521 /* Stop clocks and reset to a fresh time control */
16522 void
16523 ResetClocks ()
16524 {
16525     (void) StopClockTimer();
16526     if (appData.icsActive) {
16527         whiteTimeRemaining = blackTimeRemaining = 0;
16528     } else if (searchTime) {
16529         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16530         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16531     } else { /* [HGM] correct new time quote for time odds */
16532         whiteTC = blackTC = fullTimeControlString;
16533         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16534         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16535     }
16536     if (whiteFlag || blackFlag) {
16537         DisplayTitle("");
16538         whiteFlag = blackFlag = FALSE;
16539     }
16540     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16541     DisplayBothClocks();
16542     adjustedClock = FALSE;
16543 }
16544
16545 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16546
16547 /* Decrement running clock by amount of time that has passed */
16548 void
16549 DecrementClocks ()
16550 {
16551     long timeRemaining;
16552     long lastTickLength, fudge;
16553     TimeMark now;
16554
16555     if (!appData.clockMode) return;
16556     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16557
16558     GetTimeMark(&now);
16559
16560     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16561
16562     /* Fudge if we woke up a little too soon */
16563     fudge = intendedTickLength - lastTickLength;
16564     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16565
16566     if (WhiteOnMove(forwardMostMove)) {
16567         if(whiteNPS >= 0) lastTickLength = 0;
16568         timeRemaining = whiteTimeRemaining -= lastTickLength;
16569         if(timeRemaining < 0 && !appData.icsActive) {
16570             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16571             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16572                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16573                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16574             }
16575         }
16576         DisplayWhiteClock(whiteTimeRemaining - fudge,
16577                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16578     } else {
16579         if(blackNPS >= 0) lastTickLength = 0;
16580         timeRemaining = blackTimeRemaining -= lastTickLength;
16581         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16582             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16583             if(suddenDeath) {
16584                 blackStartMove = forwardMostMove;
16585                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16586             }
16587         }
16588         DisplayBlackClock(blackTimeRemaining - fudge,
16589                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16590     }
16591     if (CheckFlags()) return;
16592
16593     if(twoBoards) { // count down secondary board's clocks as well
16594         activePartnerTime -= lastTickLength;
16595         partnerUp = 1;
16596         if(activePartner == 'W')
16597             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16598         else
16599             DisplayBlackClock(activePartnerTime, TRUE);
16600         partnerUp = 0;
16601     }
16602
16603     tickStartTM = now;
16604     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16605     StartClockTimer(intendedTickLength);
16606
16607     /* if the time remaining has fallen below the alarm threshold, sound the
16608      * alarm. if the alarm has sounded and (due to a takeback or time control
16609      * with increment) the time remaining has increased to a level above the
16610      * threshold, reset the alarm so it can sound again.
16611      */
16612
16613     if (appData.icsActive && appData.icsAlarm) {
16614
16615         /* make sure we are dealing with the user's clock */
16616         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16617                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16618            )) return;
16619
16620         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16621             alarmSounded = FALSE;
16622         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16623             PlayAlarmSound();
16624             alarmSounded = TRUE;
16625         }
16626     }
16627 }
16628
16629
16630 /* A player has just moved, so stop the previously running
16631    clock and (if in clock mode) start the other one.
16632    We redisplay both clocks in case we're in ICS mode, because
16633    ICS gives us an update to both clocks after every move.
16634    Note that this routine is called *after* forwardMostMove
16635    is updated, so the last fractional tick must be subtracted
16636    from the color that is *not* on move now.
16637 */
16638 void
16639 SwitchClocks (int newMoveNr)
16640 {
16641     long lastTickLength;
16642     TimeMark now;
16643     int flagged = FALSE;
16644
16645     GetTimeMark(&now);
16646
16647     if (StopClockTimer() && appData.clockMode) {
16648         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16649         if (!WhiteOnMove(forwardMostMove)) {
16650             if(blackNPS >= 0) lastTickLength = 0;
16651             blackTimeRemaining -= lastTickLength;
16652            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16653 //         if(pvInfoList[forwardMostMove].time == -1)
16654                  pvInfoList[forwardMostMove].time =               // use GUI time
16655                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16656         } else {
16657            if(whiteNPS >= 0) lastTickLength = 0;
16658            whiteTimeRemaining -= lastTickLength;
16659            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16660 //         if(pvInfoList[forwardMostMove].time == -1)
16661                  pvInfoList[forwardMostMove].time =
16662                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16663         }
16664         flagged = CheckFlags();
16665     }
16666     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16667     CheckTimeControl();
16668
16669     if (flagged || !appData.clockMode) return;
16670
16671     switch (gameMode) {
16672       case MachinePlaysBlack:
16673       case MachinePlaysWhite:
16674       case BeginningOfGame:
16675         if (pausing) return;
16676         break;
16677
16678       case EditGame:
16679       case PlayFromGameFile:
16680       case IcsExamining:
16681         return;
16682
16683       default:
16684         break;
16685     }
16686
16687     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16688         if(WhiteOnMove(forwardMostMove))
16689              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16690         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16691     }
16692
16693     tickStartTM = now;
16694     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16695       whiteTimeRemaining : blackTimeRemaining);
16696     StartClockTimer(intendedTickLength);
16697 }
16698
16699
16700 /* Stop both clocks */
16701 void
16702 StopClocks ()
16703 {
16704     long lastTickLength;
16705     TimeMark now;
16706
16707     if (!StopClockTimer()) return;
16708     if (!appData.clockMode) return;
16709
16710     GetTimeMark(&now);
16711
16712     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16713     if (WhiteOnMove(forwardMostMove)) {
16714         if(whiteNPS >= 0) lastTickLength = 0;
16715         whiteTimeRemaining -= lastTickLength;
16716         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16717     } else {
16718         if(blackNPS >= 0) lastTickLength = 0;
16719         blackTimeRemaining -= lastTickLength;
16720         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16721     }
16722     CheckFlags();
16723 }
16724
16725 /* Start clock of player on move.  Time may have been reset, so
16726    if clock is already running, stop and restart it. */
16727 void
16728 StartClocks ()
16729 {
16730     (void) StopClockTimer(); /* in case it was running already */
16731     DisplayBothClocks();
16732     if (CheckFlags()) return;
16733
16734     if (!appData.clockMode) return;
16735     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16736
16737     GetTimeMark(&tickStartTM);
16738     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16739       whiteTimeRemaining : blackTimeRemaining);
16740
16741    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16742     whiteNPS = blackNPS = -1;
16743     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16744        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16745         whiteNPS = first.nps;
16746     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16747        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16748         blackNPS = first.nps;
16749     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16750         whiteNPS = second.nps;
16751     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16752         blackNPS = second.nps;
16753     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16754
16755     StartClockTimer(intendedTickLength);
16756 }
16757
16758 char *
16759 TimeString (long ms)
16760 {
16761     long second, minute, hour, day;
16762     char *sign = "";
16763     static char buf[32];
16764
16765     if (ms > 0 && ms <= 9900) {
16766       /* convert milliseconds to tenths, rounding up */
16767       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16768
16769       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16770       return buf;
16771     }
16772
16773     /* convert milliseconds to seconds, rounding up */
16774     /* use floating point to avoid strangeness of integer division
16775        with negative dividends on many machines */
16776     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16777
16778     if (second < 0) {
16779         sign = "-";
16780         second = -second;
16781     }
16782
16783     day = second / (60 * 60 * 24);
16784     second = second % (60 * 60 * 24);
16785     hour = second / (60 * 60);
16786     second = second % (60 * 60);
16787     minute = second / 60;
16788     second = second % 60;
16789
16790     if (day > 0)
16791       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16792               sign, day, hour, minute, second);
16793     else if (hour > 0)
16794       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16795     else
16796       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16797
16798     return buf;
16799 }
16800
16801
16802 /*
16803  * This is necessary because some C libraries aren't ANSI C compliant yet.
16804  */
16805 char *
16806 StrStr (char *string, char *match)
16807 {
16808     int i, length;
16809
16810     length = strlen(match);
16811
16812     for (i = strlen(string) - length; i >= 0; i--, string++)
16813       if (!strncmp(match, string, length))
16814         return string;
16815
16816     return NULL;
16817 }
16818
16819 char *
16820 StrCaseStr (char *string, char *match)
16821 {
16822     int i, j, length;
16823
16824     length = strlen(match);
16825
16826     for (i = strlen(string) - length; i >= 0; i--, string++) {
16827         for (j = 0; j < length; j++) {
16828             if (ToLower(match[j]) != ToLower(string[j]))
16829               break;
16830         }
16831         if (j == length) return string;
16832     }
16833
16834     return NULL;
16835 }
16836
16837 #ifndef _amigados
16838 int
16839 StrCaseCmp (char *s1, char *s2)
16840 {
16841     char c1, c2;
16842
16843     for (;;) {
16844         c1 = ToLower(*s1++);
16845         c2 = ToLower(*s2++);
16846         if (c1 > c2) return 1;
16847         if (c1 < c2) return -1;
16848         if (c1 == NULLCHAR) return 0;
16849     }
16850 }
16851
16852
16853 int
16854 ToLower (int c)
16855 {
16856     return isupper(c) ? tolower(c) : c;
16857 }
16858
16859
16860 int
16861 ToUpper (int c)
16862 {
16863     return islower(c) ? toupper(c) : c;
16864 }
16865 #endif /* !_amigados    */
16866
16867 char *
16868 StrSave (char *s)
16869 {
16870   char *ret;
16871
16872   if ((ret = (char *) malloc(strlen(s) + 1)))
16873     {
16874       safeStrCpy(ret, s, strlen(s)+1);
16875     }
16876   return ret;
16877 }
16878
16879 char *
16880 StrSavePtr (char *s, char **savePtr)
16881 {
16882     if (*savePtr) {
16883         free(*savePtr);
16884     }
16885     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16886       safeStrCpy(*savePtr, s, strlen(s)+1);
16887     }
16888     return(*savePtr);
16889 }
16890
16891 char *
16892 PGNDate ()
16893 {
16894     time_t clock;
16895     struct tm *tm;
16896     char buf[MSG_SIZ];
16897
16898     clock = time((time_t *)NULL);
16899     tm = localtime(&clock);
16900     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16901             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16902     return StrSave(buf);
16903 }
16904
16905
16906 char *
16907 PositionToFEN (int move, char *overrideCastling)
16908 {
16909     int i, j, fromX, fromY, toX, toY;
16910     int whiteToPlay;
16911     char buf[MSG_SIZ];
16912     char *p, *q;
16913     int emptycount;
16914     ChessSquare piece;
16915
16916     whiteToPlay = (gameMode == EditPosition) ?
16917       !blackPlaysFirst : (move % 2 == 0);
16918     p = buf;
16919
16920     /* Piece placement data */
16921     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16922         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16923         emptycount = 0;
16924         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16925             if (boards[move][i][j] == EmptySquare) {
16926                 emptycount++;
16927             } else { ChessSquare piece = boards[move][i][j];
16928                 if (emptycount > 0) {
16929                     if(emptycount<10) /* [HGM] can be >= 10 */
16930                         *p++ = '0' + emptycount;
16931                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16932                     emptycount = 0;
16933                 }
16934                 if(PieceToChar(piece) == '+') {
16935                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16936                     *p++ = '+';
16937                     piece = (ChessSquare)(DEMOTED piece);
16938                 }
16939                 *p++ = PieceToChar(piece);
16940                 if(p[-1] == '~') {
16941                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16942                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16943                     *p++ = '~';
16944                 }
16945             }
16946         }
16947         if (emptycount > 0) {
16948             if(emptycount<10) /* [HGM] can be >= 10 */
16949                 *p++ = '0' + emptycount;
16950             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16951             emptycount = 0;
16952         }
16953         *p++ = '/';
16954     }
16955     *(p - 1) = ' ';
16956
16957     /* [HGM] print Crazyhouse or Shogi holdings */
16958     if( gameInfo.holdingsWidth ) {
16959         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16960         q = p;
16961         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16962             piece = boards[move][i][BOARD_WIDTH-1];
16963             if( piece != EmptySquare )
16964               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16965                   *p++ = PieceToChar(piece);
16966         }
16967         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16968             piece = boards[move][BOARD_HEIGHT-i-1][0];
16969             if( piece != EmptySquare )
16970               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16971                   *p++ = PieceToChar(piece);
16972         }
16973
16974         if( q == p ) *p++ = '-';
16975         *p++ = ']';
16976         *p++ = ' ';
16977     }
16978
16979     /* Active color */
16980     *p++ = whiteToPlay ? 'w' : 'b';
16981     *p++ = ' ';
16982
16983   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16984     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16985   } else {
16986   if(nrCastlingRights) {
16987      q = p;
16988      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16989        /* [HGM] write directly from rights */
16990            if(boards[move][CASTLING][2] != NoRights &&
16991               boards[move][CASTLING][0] != NoRights   )
16992                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16993            if(boards[move][CASTLING][2] != NoRights &&
16994               boards[move][CASTLING][1] != NoRights   )
16995                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16996            if(boards[move][CASTLING][5] != NoRights &&
16997               boards[move][CASTLING][3] != NoRights   )
16998                 *p++ = boards[move][CASTLING][3] + AAA;
16999            if(boards[move][CASTLING][5] != NoRights &&
17000               boards[move][CASTLING][4] != NoRights   )
17001                 *p++ = boards[move][CASTLING][4] + AAA;
17002      } else {
17003
17004         /* [HGM] write true castling rights */
17005         if( nrCastlingRights == 6 ) {
17006             int q, k=0;
17007             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17008                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17009             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17010                  boards[move][CASTLING][2] != NoRights  );
17011             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17012                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17013                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17014                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17015                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17016             }
17017             if(q) *p++ = 'Q';
17018             k = 0;
17019             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17020                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17021             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17022                  boards[move][CASTLING][5] != NoRights  );
17023             if(gameInfo.variant == VariantSChess) {
17024                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17025                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17026                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17027                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17028             }
17029             if(q) *p++ = 'q';
17030         }
17031      }
17032      if (q == p) *p++ = '-'; /* No castling rights */
17033      *p++ = ' ';
17034   }
17035
17036   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17037      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17038     /* En passant target square */
17039     if (move > backwardMostMove) {
17040         fromX = moveList[move - 1][0] - AAA;
17041         fromY = moveList[move - 1][1] - ONE;
17042         toX = moveList[move - 1][2] - AAA;
17043         toY = moveList[move - 1][3] - ONE;
17044         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17045             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17046             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17047             fromX == toX) {
17048             /* 2-square pawn move just happened */
17049             *p++ = toX + AAA;
17050             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17051         } else {
17052             *p++ = '-';
17053         }
17054     } else if(move == backwardMostMove) {
17055         // [HGM] perhaps we should always do it like this, and forget the above?
17056         if((signed char)boards[move][EP_STATUS] >= 0) {
17057             *p++ = boards[move][EP_STATUS] + AAA;
17058             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17059         } else {
17060             *p++ = '-';
17061         }
17062     } else {
17063         *p++ = '-';
17064     }
17065     *p++ = ' ';
17066   }
17067   }
17068
17069     /* [HGM] find reversible plies */
17070     {   int i = 0, j=move;
17071
17072         if (appData.debugMode) { int k;
17073             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17074             for(k=backwardMostMove; k<=forwardMostMove; k++)
17075                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17076
17077         }
17078
17079         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17080         if( j == backwardMostMove ) i += initialRulePlies;
17081         sprintf(p, "%d ", i);
17082         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17083     }
17084     /* Fullmove number */
17085     sprintf(p, "%d", (move / 2) + 1);
17086
17087     return StrSave(buf);
17088 }
17089
17090 Boolean
17091 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17092 {
17093     int i, j;
17094     char *p, c;
17095     int emptycount, virgin[BOARD_FILES];
17096     ChessSquare piece;
17097
17098     p = fen;
17099
17100     /* [HGM] by default clear Crazyhouse holdings, if present */
17101     if(gameInfo.holdingsWidth) {
17102        for(i=0; i<BOARD_HEIGHT; i++) {
17103            board[i][0]             = EmptySquare; /* black holdings */
17104            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17105            board[i][1]             = (ChessSquare) 0; /* black counts */
17106            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17107        }
17108     }
17109
17110     /* Piece placement data */
17111     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17112         j = 0;
17113         for (;;) {
17114             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17115                 if (*p == '/') p++;
17116                 emptycount = gameInfo.boardWidth - j;
17117                 while (emptycount--)
17118                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17119                 break;
17120 #if(BOARD_FILES >= 10)
17121             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17122                 p++; emptycount=10;
17123                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17124                 while (emptycount--)
17125                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17126 #endif
17127             } else if (isdigit(*p)) {
17128                 emptycount = *p++ - '0';
17129                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17130                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17131                 while (emptycount--)
17132                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17133             } else if (*p == '+' || isalpha(*p)) {
17134                 if (j >= gameInfo.boardWidth) return FALSE;
17135                 if(*p=='+') {
17136                     piece = CharToPiece(*++p);
17137                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17138                     piece = (ChessSquare) (PROMOTED piece ); p++;
17139                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17140                 } else piece = CharToPiece(*p++);
17141
17142                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17143                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17144                     piece = (ChessSquare) (PROMOTED piece);
17145                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17146                     p++;
17147                 }
17148                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17149             } else {
17150                 return FALSE;
17151             }
17152         }
17153     }
17154     while (*p == '/' || *p == ' ') p++;
17155
17156     /* [HGM] look for Crazyhouse holdings here */
17157     while(*p==' ') p++;
17158     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17159         if(*p == '[') p++;
17160         if(*p == '-' ) p++; /* empty holdings */ else {
17161             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17162             /* if we would allow FEN reading to set board size, we would   */
17163             /* have to add holdings and shift the board read so far here   */
17164             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17165                 p++;
17166                 if((int) piece >= (int) BlackPawn ) {
17167                     i = (int)piece - (int)BlackPawn;
17168                     i = PieceToNumber((ChessSquare)i);
17169                     if( i >= gameInfo.holdingsSize ) return FALSE;
17170                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17171                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17172                 } else {
17173                     i = (int)piece - (int)WhitePawn;
17174                     i = PieceToNumber((ChessSquare)i);
17175                     if( i >= gameInfo.holdingsSize ) return FALSE;
17176                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17177                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17178                 }
17179             }
17180         }
17181         if(*p == ']') p++;
17182     }
17183
17184     while(*p == ' ') p++;
17185
17186     /* Active color */
17187     c = *p++;
17188     if(appData.colorNickNames) {
17189       if( c == appData.colorNickNames[0] ) c = 'w'; else
17190       if( c == appData.colorNickNames[1] ) c = 'b';
17191     }
17192     switch (c) {
17193       case 'w':
17194         *blackPlaysFirst = FALSE;
17195         break;
17196       case 'b':
17197         *blackPlaysFirst = TRUE;
17198         break;
17199       default:
17200         return FALSE;
17201     }
17202
17203     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17204     /* return the extra info in global variiables             */
17205
17206     /* set defaults in case FEN is incomplete */
17207     board[EP_STATUS] = EP_UNKNOWN;
17208     for(i=0; i<nrCastlingRights; i++ ) {
17209         board[CASTLING][i] =
17210             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17211     }   /* assume possible unless obviously impossible */
17212     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17213     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17214     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17215                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17216     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17217     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17218     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17219                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17220     FENrulePlies = 0;
17221
17222     while(*p==' ') p++;
17223     if(nrCastlingRights) {
17224       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17225       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17226           /* castling indicator present, so default becomes no castlings */
17227           for(i=0; i<nrCastlingRights; i++ ) {
17228                  board[CASTLING][i] = NoRights;
17229           }
17230       }
17231       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17232              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17233              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17234              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17235         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17236
17237         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17238             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17239             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17240         }
17241         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17242             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17243         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17244                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17245         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17246                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17247         switch(c) {
17248           case'K':
17249               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17250               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17251               board[CASTLING][2] = whiteKingFile;
17252               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17253               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17254               break;
17255           case'Q':
17256               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17257               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17258               board[CASTLING][2] = whiteKingFile;
17259               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17260               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17261               break;
17262           case'k':
17263               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17264               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17265               board[CASTLING][5] = blackKingFile;
17266               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17267               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17268               break;
17269           case'q':
17270               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17271               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17272               board[CASTLING][5] = blackKingFile;
17273               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17274               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17275           case '-':
17276               break;
17277           default: /* FRC castlings */
17278               if(c >= 'a') { /* black rights */
17279                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17280                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17281                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17282                   if(i == BOARD_RGHT) break;
17283                   board[CASTLING][5] = i;
17284                   c -= AAA;
17285                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17286                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17287                   if(c > i)
17288                       board[CASTLING][3] = c;
17289                   else
17290                       board[CASTLING][4] = c;
17291               } else { /* white rights */
17292                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17293                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17294                     if(board[0][i] == WhiteKing) break;
17295                   if(i == BOARD_RGHT) break;
17296                   board[CASTLING][2] = i;
17297                   c -= AAA - 'a' + 'A';
17298                   if(board[0][c] >= WhiteKing) break;
17299                   if(c > i)
17300                       board[CASTLING][0] = c;
17301                   else
17302                       board[CASTLING][1] = c;
17303               }
17304         }
17305       }
17306       for(i=0; i<nrCastlingRights; i++)
17307         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17308       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17309     if (appData.debugMode) {
17310         fprintf(debugFP, "FEN castling rights:");
17311         for(i=0; i<nrCastlingRights; i++)
17312         fprintf(debugFP, " %d", board[CASTLING][i]);
17313         fprintf(debugFP, "\n");
17314     }
17315
17316       while(*p==' ') p++;
17317     }
17318
17319     /* read e.p. field in games that know e.p. capture */
17320     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17321        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17322       if(*p=='-') {
17323         p++; board[EP_STATUS] = EP_NONE;
17324       } else {
17325          char c = *p++ - AAA;
17326
17327          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17328          if(*p >= '0' && *p <='9') p++;
17329          board[EP_STATUS] = c;
17330       }
17331     }
17332
17333
17334     if(sscanf(p, "%d", &i) == 1) {
17335         FENrulePlies = i; /* 50-move ply counter */
17336         /* (The move number is still ignored)    */
17337     }
17338
17339     return TRUE;
17340 }
17341
17342 void
17343 EditPositionPasteFEN (char *fen)
17344 {
17345   if (fen != NULL) {
17346     Board initial_position;
17347
17348     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17349       DisplayError(_("Bad FEN position in clipboard"), 0);
17350       return ;
17351     } else {
17352       int savedBlackPlaysFirst = blackPlaysFirst;
17353       EditPositionEvent();
17354       blackPlaysFirst = savedBlackPlaysFirst;
17355       CopyBoard(boards[0], initial_position);
17356       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17357       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17358       DisplayBothClocks();
17359       DrawPosition(FALSE, boards[currentMove]);
17360     }
17361   }
17362 }
17363
17364 static char cseq[12] = "\\   ";
17365
17366 Boolean
17367 set_cont_sequence (char *new_seq)
17368 {
17369     int len;
17370     Boolean ret;
17371
17372     // handle bad attempts to set the sequence
17373         if (!new_seq)
17374                 return 0; // acceptable error - no debug
17375
17376     len = strlen(new_seq);
17377     ret = (len > 0) && (len < sizeof(cseq));
17378     if (ret)
17379       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17380     else if (appData.debugMode)
17381       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17382     return ret;
17383 }
17384
17385 /*
17386     reformat a source message so words don't cross the width boundary.  internal
17387     newlines are not removed.  returns the wrapped size (no null character unless
17388     included in source message).  If dest is NULL, only calculate the size required
17389     for the dest buffer.  lp argument indicats line position upon entry, and it's
17390     passed back upon exit.
17391 */
17392 int
17393 wrap (char *dest, char *src, int count, int width, int *lp)
17394 {
17395     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17396
17397     cseq_len = strlen(cseq);
17398     old_line = line = *lp;
17399     ansi = len = clen = 0;
17400
17401     for (i=0; i < count; i++)
17402     {
17403         if (src[i] == '\033')
17404             ansi = 1;
17405
17406         // if we hit the width, back up
17407         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17408         {
17409             // store i & len in case the word is too long
17410             old_i = i, old_len = len;
17411
17412             // find the end of the last word
17413             while (i && src[i] != ' ' && src[i] != '\n')
17414             {
17415                 i--;
17416                 len--;
17417             }
17418
17419             // word too long?  restore i & len before splitting it
17420             if ((old_i-i+clen) >= width)
17421             {
17422                 i = old_i;
17423                 len = old_len;
17424             }
17425
17426             // extra space?
17427             if (i && src[i-1] == ' ')
17428                 len--;
17429
17430             if (src[i] != ' ' && src[i] != '\n')
17431             {
17432                 i--;
17433                 if (len)
17434                     len--;
17435             }
17436
17437             // now append the newline and continuation sequence
17438             if (dest)
17439                 dest[len] = '\n';
17440             len++;
17441             if (dest)
17442                 strncpy(dest+len, cseq, cseq_len);
17443             len += cseq_len;
17444             line = cseq_len;
17445             clen = cseq_len;
17446             continue;
17447         }
17448
17449         if (dest)
17450             dest[len] = src[i];
17451         len++;
17452         if (!ansi)
17453             line++;
17454         if (src[i] == '\n')
17455             line = 0;
17456         if (src[i] == 'm')
17457             ansi = 0;
17458     }
17459     if (dest && appData.debugMode)
17460     {
17461         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17462             count, width, line, len, *lp);
17463         show_bytes(debugFP, src, count);
17464         fprintf(debugFP, "\ndest: ");
17465         show_bytes(debugFP, dest, len);
17466         fprintf(debugFP, "\n");
17467     }
17468     *lp = dest ? line : old_line;
17469
17470     return len;
17471 }
17472
17473 // [HGM] vari: routines for shelving variations
17474 Boolean modeRestore = FALSE;
17475
17476 void
17477 PushInner (int firstMove, int lastMove)
17478 {
17479         int i, j, nrMoves = lastMove - firstMove;
17480
17481         // push current tail of game on stack
17482         savedResult[storedGames] = gameInfo.result;
17483         savedDetails[storedGames] = gameInfo.resultDetails;
17484         gameInfo.resultDetails = NULL;
17485         savedFirst[storedGames] = firstMove;
17486         savedLast [storedGames] = lastMove;
17487         savedFramePtr[storedGames] = framePtr;
17488         framePtr -= nrMoves; // reserve space for the boards
17489         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17490             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17491             for(j=0; j<MOVE_LEN; j++)
17492                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17493             for(j=0; j<2*MOVE_LEN; j++)
17494                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17495             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17496             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17497             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17498             pvInfoList[firstMove+i-1].depth = 0;
17499             commentList[framePtr+i] = commentList[firstMove+i];
17500             commentList[firstMove+i] = NULL;
17501         }
17502
17503         storedGames++;
17504         forwardMostMove = firstMove; // truncate game so we can start variation
17505 }
17506
17507 void
17508 PushTail (int firstMove, int lastMove)
17509 {
17510         if(appData.icsActive) { // only in local mode
17511                 forwardMostMove = currentMove; // mimic old ICS behavior
17512                 return;
17513         }
17514         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17515
17516         PushInner(firstMove, lastMove);
17517         if(storedGames == 1) GreyRevert(FALSE);
17518         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17519 }
17520
17521 void
17522 PopInner (Boolean annotate)
17523 {
17524         int i, j, nrMoves;
17525         char buf[8000], moveBuf[20];
17526
17527         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17528         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17529         nrMoves = savedLast[storedGames] - currentMove;
17530         if(annotate) {
17531                 int cnt = 10;
17532                 if(!WhiteOnMove(currentMove))
17533                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17534                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17535                 for(i=currentMove; i<forwardMostMove; i++) {
17536                         if(WhiteOnMove(i))
17537                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17538                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17539                         strcat(buf, moveBuf);
17540                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17541                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17542                 }
17543                 strcat(buf, ")");
17544         }
17545         for(i=1; i<=nrMoves; i++) { // copy last variation back
17546             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17547             for(j=0; j<MOVE_LEN; j++)
17548                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17549             for(j=0; j<2*MOVE_LEN; j++)
17550                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17551             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17552             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17553             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17554             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17555             commentList[currentMove+i] = commentList[framePtr+i];
17556             commentList[framePtr+i] = NULL;
17557         }
17558         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17559         framePtr = savedFramePtr[storedGames];
17560         gameInfo.result = savedResult[storedGames];
17561         if(gameInfo.resultDetails != NULL) {
17562             free(gameInfo.resultDetails);
17563       }
17564         gameInfo.resultDetails = savedDetails[storedGames];
17565         forwardMostMove = currentMove + nrMoves;
17566 }
17567
17568 Boolean
17569 PopTail (Boolean annotate)
17570 {
17571         if(appData.icsActive) return FALSE; // only in local mode
17572         if(!storedGames) return FALSE; // sanity
17573         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17574
17575         PopInner(annotate);
17576         if(currentMove < forwardMostMove) ForwardEvent(); else
17577         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17578
17579         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17580         return TRUE;
17581 }
17582
17583 void
17584 CleanupTail ()
17585 {       // remove all shelved variations
17586         int i;
17587         for(i=0; i<storedGames; i++) {
17588             if(savedDetails[i])
17589                 free(savedDetails[i]);
17590             savedDetails[i] = NULL;
17591         }
17592         for(i=framePtr; i<MAX_MOVES; i++) {
17593                 if(commentList[i]) free(commentList[i]);
17594                 commentList[i] = NULL;
17595         }
17596         framePtr = MAX_MOVES-1;
17597         storedGames = 0;
17598 }
17599
17600 void
17601 LoadVariation (int index, char *text)
17602 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17603         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17604         int level = 0, move;
17605
17606         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17607         // first find outermost bracketing variation
17608         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17609             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17610                 if(*p == '{') wait = '}'; else
17611                 if(*p == '[') wait = ']'; else
17612                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17613                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17614             }
17615             if(*p == wait) wait = NULLCHAR; // closing ]} found
17616             p++;
17617         }
17618         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17619         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17620         end[1] = NULLCHAR; // clip off comment beyond variation
17621         ToNrEvent(currentMove-1);
17622         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17623         // kludge: use ParsePV() to append variation to game
17624         move = currentMove;
17625         ParsePV(start, TRUE, TRUE);
17626         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17627         ClearPremoveHighlights();
17628         CommentPopDown();
17629         ToNrEvent(currentMove+1);
17630 }
17631
17632 void
17633 LoadTheme ()
17634 {
17635     char *p, *q, buf[MSG_SIZ];
17636     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17637         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17638         ParseArgsFromString(buf);
17639         ActivateTheme(TRUE); // also redo colors
17640         return;
17641     }
17642     p = nickName;
17643     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17644     {
17645         int len;
17646         q = appData.themeNames;
17647         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17648       if(appData.useBitmaps) {
17649         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17650                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17651                 appData.liteBackTextureMode,
17652                 appData.darkBackTextureMode );
17653       } else {
17654         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17655                 Col2Text(2),   // lightSquareColor
17656                 Col2Text(3) ); // darkSquareColor
17657       }
17658       if(appData.useBorder) {
17659         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17660                 appData.border);
17661       } else {
17662         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17663       }
17664       if(appData.useFont) {
17665         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17666                 appData.renderPiecesWithFont,
17667                 appData.fontToPieceTable,
17668                 Col2Text(9),    // appData.fontBackColorWhite
17669                 Col2Text(10) ); // appData.fontForeColorBlack
17670       } else {
17671         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17672                 appData.pieceDirectory);
17673         if(!appData.pieceDirectory[0])
17674           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17675                 Col2Text(0),   // whitePieceColor
17676                 Col2Text(1) ); // blackPieceColor
17677       }
17678       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17679                 Col2Text(4),   // highlightSquareColor
17680                 Col2Text(5) ); // premoveHighlightColor
17681         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17682         if(insert != q) insert[-1] = NULLCHAR;
17683         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17684         if(q)   free(q);
17685     }
17686     ActivateTheme(FALSE);
17687 }