Resend engine-defined options after reuse=0 reload
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 Boolean abortMatch;
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
276
277 /* States for ics_getting_history */
278 #define H_FALSE 0
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
284
285 /* whosays values for GameEnds */
286 #define GE_ICS 0
287 #define GE_ENGINE 1
288 #define GE_PLAYER 2
289 #define GE_FILE 3
290 #define GE_XBOARD 4
291 #define GE_ENGINE1 5
292 #define GE_ENGINE2 6
293
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
296
297 /* Different types of move when calling RegisterMove */
298 #define CMAIL_MOVE   0
299 #define CMAIL_RESIGN 1
300 #define CMAIL_DRAW   2
301 #define CMAIL_ACCEPT 3
302
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
307
308 /* Telnet protocol constants */
309 #define TN_WILL 0373
310 #define TN_WONT 0374
311 #define TN_DO   0375
312 #define TN_DONT 0376
313 #define TN_IAC  0377
314 #define TN_ECHO 0001
315 #define TN_SGA  0003
316 #define TN_PORT 23
317
318 char*
319 safeStrCpy (char *dst, const char *src, size_t count)
320 { // [HGM] made safe
321   int i;
322   assert( dst != NULL );
323   assert( src != NULL );
324   assert( count > 0 );
325
326   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327   if(  i == count && dst[count-1] != NULLCHAR)
328     {
329       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330       if(appData.debugMode)
331       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
332     }
333
334   return dst;
335 }
336
337 /* Some compiler can't cast u64 to double
338  * This function do the job for us:
339
340  * We use the highest bit for cast, this only
341  * works if the highest bit is not
342  * in use (This should not happen)
343  *
344  * We used this for all compiler
345  */
346 double
347 u64ToDouble (u64 value)
348 {
349   double r;
350   u64 tmp = value & u64Const(0x7fffffffffffffff);
351   r = (double)(s64)tmp;
352   if (value & u64Const(0x8000000000000000))
353        r +=  9.2233720368547758080e18; /* 2^63 */
354  return r;
355 }
356
357 /* Fake up flags for now, as we aren't keeping track of castling
358    availability yet. [HGM] Change of logic: the flag now only
359    indicates the type of castlings allowed by the rule of the game.
360    The actual rights themselves are maintained in the array
361    castlingRights, as part of the game history, and are not probed
362    by this function.
363  */
364 int
365 PosFlags (index)
366 {
367   int flags = F_ALL_CASTLE_OK;
368   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369   switch (gameInfo.variant) {
370   case VariantSuicide:
371     flags &= ~F_ALL_CASTLE_OK;
372   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373     flags |= F_IGNORE_CHECK;
374   case VariantLosers:
375     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376     break;
377   case VariantAtomic:
378     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379     break;
380   case VariantKriegspiel:
381     flags |= F_KRIEGSPIEL_CAPTURE;
382     break;
383   case VariantCapaRandom:
384   case VariantFischeRandom:
385     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386   case VariantNoCastle:
387   case VariantShatranj:
388   case VariantCourier:
389   case VariantMakruk:
390   case VariantGrand:
391     flags &= ~F_ALL_CASTLE_OK;
392     break;
393   default:
394     break;
395   }
396   return flags;
397 }
398
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
401
402 /*
403     [AS] Note: sometimes, the sscanf() function is used to parse the input
404     into a fixed-size buffer. Because of this, we must be prepared to
405     receive strings as long as the size of the input buffer, which is currently
406     set to 4K for Windows and 8K for the rest.
407     So, we must either allocate sufficiently large buffers here, or
408     reduce the size of the input buffer in the input reading part.
409 */
410
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
414
415 ChessProgramState first, second, pairing;
416
417 /* premove variables */
418 int premoveToX = 0;
419 int premoveToY = 0;
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
423 int gotPremove = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
426
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
429
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
457
458 int have_sent_ICS_logon = 0;
459 int movesPerSession;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
471
472 /* animateTraining preserves the state of appData.animate
473  * when Training mode is activated. This allows the
474  * response to be animated when appData.animate == TRUE and
475  * appData.animateDragging == TRUE.
476  */
477 Boolean animateTraining;
478
479 GameInfo gameInfo;
480
481 AppData appData;
482
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char  initialRights[BOARD_FILES];
487 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int   initialRulePlies, FENrulePlies;
489 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
490 int loadFlag = 0;
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
493
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
497 int storedGames = 0;
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
503
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
509
510 ChessSquare  FIDEArray[2][BOARD_FILES] = {
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514         BlackKing, BlackBishop, BlackKnight, BlackRook }
515 };
516
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackKing, BlackKnight, BlackRook }
522 };
523
524 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527     { BlackRook, BlackMan, BlackBishop, BlackQueen,
528         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 };
530
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 };
537
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 };
544
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 };
551
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackMan, BlackFerz,
556         BlackKing, BlackMan, BlackKnight, BlackRook }
557 };
558
559
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 };
567
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 };
574
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 };
581
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 };
588
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 };
595
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 };
602
603 #ifdef GOTHIC
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 };
610 #else // !GOTHIC
611 #define GothicArray CapablancaArray
612 #endif // !GOTHIC
613
614 #ifdef FALCON
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
620 };
621 #else // !FALCON
622 #define FalconArray CapablancaArray
623 #endif // !FALCON
624
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
631
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
638 };
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
642
643
644 Board initialPosition;
645
646
647 /* Convert str to a rating. Checks for special cases of "----",
648
649    "++++", etc. Also strips ()'s */
650 int
651 string_to_rating (char *str)
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats ()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit ()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine (ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions (ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("first"),
744   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 N_("second")
747 };
748
749 void
750 InitEngine (ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789     cps->reload = FALSE;
790
791     /* New features added by Tord: */
792     cps->useFEN960 = FALSE;
793     cps->useOOCastle = TRUE;
794     /* End of new features added by Tord. */
795     cps->fenOverride  = appData.fenOverride[n];
796
797     /* [HGM] time odds: set factor for each machine */
798     cps->timeOdds  = appData.timeOdds[n];
799
800     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801     cps->accumulateTC = appData.accumulateTC[n];
802     cps->maxNrOfSessions = 1;
803
804     /* [HGM] debug */
805     cps->debug = FALSE;
806
807     cps->supportsNPS = UNKNOWN;
808     cps->memSize = FALSE;
809     cps->maxCores = FALSE;
810     cps->egtFormats[0] = NULLCHAR;
811
812     /* [HGM] options */
813     cps->optionSettings  = appData.engOptions[n];
814
815     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816     cps->isUCI = appData.isUCI[n]; /* [AS] */
817     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
818
819     if (appData.protocolVersion[n] > PROTOVER
820         || appData.protocolVersion[n] < 1)
821       {
822         char buf[MSG_SIZ];
823         int len;
824
825         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826                        appData.protocolVersion[n]);
827         if( (len >= MSG_SIZ) && appData.debugMode )
828           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
829
830         DisplayFatalError(buf, 0, 2);
831       }
832     else
833       {
834         cps->protocolVersion = appData.protocolVersion[n];
835       }
836
837     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
838     ParseFeatures(appData.featureDefaults, cps);
839 }
840
841 ChessProgramState *savCps;
842
843 void
844 LoadEngine ()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine (ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] =
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
883         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
884         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
885
886 void
887 FloatToFront(char **list, char *engineLine)
888 {
889     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
890     int i=0;
891     if(appData.recentEngines <= 0) return;
892     TidyProgramName(engineLine, "localhost", tidy+1);
893     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
894     strncpy(buf+1, *list, MSG_SIZ-50);
895     if(p = strstr(buf, tidy)) { // tidy name appears in list
896         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
897         while(*p++ = *++q); // squeeze out
898     }
899     strcat(tidy, buf+1); // put list behind tidy name
900     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
901     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
902     ASSIGN(*list, tidy+1);
903 }
904
905 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
906
907 void
908 Load (ChessProgramState *cps, int i)
909 {
910     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
911     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
912         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
913         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
914         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
915         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
916         appData.firstProtocolVersion = PROTOVER;
917         ParseArgsFromString(buf);
918         SwapEngines(i);
919         ReplaceEngine(cps, i);
920         FloatToFront(&appData.recentEngineList, engineLine);
921         return;
922     }
923     p = engineName;
924     while(q = strchr(p, SLASH)) p = q+1;
925     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
926     if(engineDir[0] != NULLCHAR) {
927         ASSIGN(appData.directory[i], engineDir); p = engineName;
928     } else if(p != engineName) { // derive directory from engine path, when not given
929         p[-1] = 0;
930         ASSIGN(appData.directory[i], engineName);
931         p[-1] = SLASH;
932         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
933     } else { ASSIGN(appData.directory[i], "."); }
934     if(params[0]) {
935         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
936         snprintf(command, MSG_SIZ, "%s %s", p, params);
937         p = command;
938     }
939     ASSIGN(appData.chessProgram[i], p);
940     appData.isUCI[i] = isUCI;
941     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
942     appData.hasOwnBookUCI[i] = hasBook;
943     if(!nickName[0]) useNick = FALSE;
944     if(useNick) ASSIGN(appData.pgnName[i], nickName);
945     if(addToList) {
946         int len;
947         char quote;
948         q = firstChessProgramNames;
949         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
950         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
951         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
952                         quote, p, quote, appData.directory[i],
953                         useNick ? " -fn \"" : "",
954                         useNick ? nickName : "",
955                         useNick ? "\"" : "",
956                         v1 ? " -firstProtocolVersion 1" : "",
957                         hasBook ? "" : " -fNoOwnBookUCI",
958                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
959                         storeVariant ? " -variant " : "",
960                         storeVariant ? VariantName(gameInfo.variant) : "");
961         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
962         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
963         if(insert != q) insert[-1] = NULLCHAR;
964         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
965         if(q)   free(q);
966         FloatToFront(&appData.recentEngineList, buf);
967     }
968     ReplaceEngine(cps, i);
969 }
970
971 void
972 InitTimeControls ()
973 {
974     int matched, min, sec;
975     /*
976      * Parse timeControl resource
977      */
978     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
979                           appData.movesPerSession)) {
980         char buf[MSG_SIZ];
981         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
982         DisplayFatalError(buf, 0, 2);
983     }
984
985     /*
986      * Parse searchTime resource
987      */
988     if (*appData.searchTime != NULLCHAR) {
989         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
990         if (matched == 1) {
991             searchTime = min * 60;
992         } else if (matched == 2) {
993             searchTime = min * 60 + sec;
994         } else {
995             char buf[MSG_SIZ];
996             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
997             DisplayFatalError(buf, 0, 2);
998         }
999     }
1000 }
1001
1002 void
1003 InitBackEnd1 ()
1004 {
1005
1006     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1007     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1008
1009     GetTimeMark(&programStartTime);
1010     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1011     appData.seedBase = random() + (random()<<15);
1012     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1013
1014     ClearProgramStats();
1015     programStats.ok_to_send = 1;
1016     programStats.seen_stat = 0;
1017
1018     /*
1019      * Initialize game list
1020      */
1021     ListNew(&gameList);
1022
1023
1024     /*
1025      * Internet chess server status
1026      */
1027     if (appData.icsActive) {
1028         appData.matchMode = FALSE;
1029         appData.matchGames = 0;
1030 #if ZIPPY
1031         appData.noChessProgram = !appData.zippyPlay;
1032 #else
1033         appData.zippyPlay = FALSE;
1034         appData.zippyTalk = FALSE;
1035         appData.noChessProgram = TRUE;
1036 #endif
1037         if (*appData.icsHelper != NULLCHAR) {
1038             appData.useTelnet = TRUE;
1039             appData.telnetProgram = appData.icsHelper;
1040         }
1041     } else {
1042         appData.zippyTalk = appData.zippyPlay = FALSE;
1043     }
1044
1045     /* [AS] Initialize pv info list [HGM] and game state */
1046     {
1047         int i, j;
1048
1049         for( i=0; i<=framePtr; i++ ) {
1050             pvInfoList[i].depth = -1;
1051             boards[i][EP_STATUS] = EP_NONE;
1052             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1053         }
1054     }
1055
1056     InitTimeControls();
1057
1058     /* [AS] Adjudication threshold */
1059     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1060
1061     InitEngine(&first, 0);
1062     InitEngine(&second, 1);
1063     CommonEngineInit();
1064
1065     pairing.which = "pairing"; // pairing engine
1066     pairing.pr = NoProc;
1067     pairing.isr = NULL;
1068     pairing.program = appData.pairingEngine;
1069     pairing.host = "localhost";
1070     pairing.dir = ".";
1071
1072     if (appData.icsActive) {
1073         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1074     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1075         appData.clockMode = FALSE;
1076         first.sendTime = second.sendTime = 0;
1077     }
1078
1079 #if ZIPPY
1080     /* Override some settings from environment variables, for backward
1081        compatibility.  Unfortunately it's not feasible to have the env
1082        vars just set defaults, at least in xboard.  Ugh.
1083     */
1084     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1085       ZippyInit();
1086     }
1087 #endif
1088
1089     if (!appData.icsActive) {
1090       char buf[MSG_SIZ];
1091       int len;
1092
1093       /* Check for variants that are supported only in ICS mode,
1094          or not at all.  Some that are accepted here nevertheless
1095          have bugs; see comments below.
1096       */
1097       VariantClass variant = StringToVariant(appData.variant);
1098       switch (variant) {
1099       case VariantBughouse:     /* need four players and two boards */
1100       case VariantKriegspiel:   /* need to hide pieces and move details */
1101         /* case VariantFischeRandom: (Fabien: moved below) */
1102         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1103         if( (len >= MSG_SIZ) && appData.debugMode )
1104           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1105
1106         DisplayFatalError(buf, 0, 2);
1107         return;
1108
1109       case VariantUnknown:
1110       case VariantLoadable:
1111       case Variant29:
1112       case Variant30:
1113       case Variant31:
1114       case Variant32:
1115       case Variant33:
1116       case Variant34:
1117       case Variant35:
1118       case Variant36:
1119       default:
1120         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1121         if( (len >= MSG_SIZ) && appData.debugMode )
1122           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1123
1124         DisplayFatalError(buf, 0, 2);
1125         return;
1126
1127       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1128       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1129       case VariantGothic:     /* [HGM] should work */
1130       case VariantCapablanca: /* [HGM] should work */
1131       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1132       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1133       case VariantKnightmate: /* [HGM] should work */
1134       case VariantCylinder:   /* [HGM] untested */
1135       case VariantFalcon:     /* [HGM] untested */
1136       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1137                                  offboard interposition not understood */
1138       case VariantNormal:     /* definitely works! */
1139       case VariantWildCastle: /* pieces not automatically shuffled */
1140       case VariantNoCastle:   /* pieces not automatically shuffled */
1141       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1142       case VariantLosers:     /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantSuicide:    /* should work except for win condition,
1145                                  and doesn't know captures are mandatory */
1146       case VariantGiveaway:   /* should work except for win condition,
1147                                  and doesn't know captures are mandatory */
1148       case VariantTwoKings:   /* should work */
1149       case VariantAtomic:     /* should work except for win condition */
1150       case Variant3Check:     /* should work except for win condition */
1151       case VariantShatranj:   /* should work except for all win conditions */
1152       case VariantMakruk:     /* should work except for draw countdown */
1153       case VariantBerolina:   /* might work if TestLegality is off */
1154       case VariantCapaRandom: /* should work */
1155       case VariantJanus:      /* should work */
1156       case VariantSuper:      /* experimental */
1157       case VariantGreat:      /* experimental, requires legality testing to be off */
1158       case VariantSChess:     /* S-Chess, should work */
1159       case VariantGrand:      /* should work */
1160       case VariantSpartan:    /* should work */
1161         break;
1162       }
1163     }
1164
1165 }
1166
1167 int
1168 NextIntegerFromString (char ** str, long * value)
1169 {
1170     int result = -1;
1171     char * s = *str;
1172
1173     while( *s == ' ' || *s == '\t' ) {
1174         s++;
1175     }
1176
1177     *value = 0;
1178
1179     if( *s >= '0' && *s <= '9' ) {
1180         while( *s >= '0' && *s <= '9' ) {
1181             *value = *value * 10 + (*s - '0');
1182             s++;
1183         }
1184
1185         result = 0;
1186     }
1187
1188     *str = s;
1189
1190     return result;
1191 }
1192
1193 int
1194 NextTimeControlFromString (char ** str, long * value)
1195 {
1196     long temp;
1197     int result = NextIntegerFromString( str, &temp );
1198
1199     if( result == 0 ) {
1200         *value = temp * 60; /* Minutes */
1201         if( **str == ':' ) {
1202             (*str)++;
1203             result = NextIntegerFromString( str, &temp );
1204             *value += temp; /* Seconds */
1205         }
1206     }
1207
1208     return result;
1209 }
1210
1211 int
1212 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1213 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1214     int result = -1, type = 0; long temp, temp2;
1215
1216     if(**str != ':') return -1; // old params remain in force!
1217     (*str)++;
1218     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1219     if( NextIntegerFromString( str, &temp ) ) return -1;
1220     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1221
1222     if(**str != '/') {
1223         /* time only: incremental or sudden-death time control */
1224         if(**str == '+') { /* increment follows; read it */
1225             (*str)++;
1226             if(**str == '!') type = *(*str)++; // Bronstein TC
1227             if(result = NextIntegerFromString( str, &temp2)) return -1;
1228             *inc = temp2 * 1000;
1229             if(**str == '.') { // read fraction of increment
1230                 char *start = ++(*str);
1231                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1232                 temp2 *= 1000;
1233                 while(start++ < *str) temp2 /= 10;
1234                 *inc += temp2;
1235             }
1236         } else *inc = 0;
1237         *moves = 0; *tc = temp * 1000; *incType = type;
1238         return 0;
1239     }
1240
1241     (*str)++; /* classical time control */
1242     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1243
1244     if(result == 0) {
1245         *moves = temp;
1246         *tc    = temp2 * 1000;
1247         *inc   = 0;
1248         *incType = type;
1249     }
1250     return result;
1251 }
1252
1253 int
1254 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1255 {   /* [HGM] get time to add from the multi-session time-control string */
1256     int incType, moves=1; /* kludge to force reading of first session */
1257     long time, increment;
1258     char *s = tcString;
1259
1260     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1261     do {
1262         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1263         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1264         if(movenr == -1) return time;    /* last move before new session     */
1265         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1266         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1267         if(!moves) return increment;     /* current session is incremental   */
1268         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1269     } while(movenr >= -1);               /* try again for next session       */
1270
1271     return 0; // no new time quota on this move
1272 }
1273
1274 int
1275 ParseTimeControl (char *tc, float ti, int mps)
1276 {
1277   long tc1;
1278   long tc2;
1279   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1280   int min, sec=0;
1281
1282   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1283   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1284       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1285   if(ti > 0) {
1286
1287     if(mps)
1288       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1289     else
1290       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1291   } else {
1292     if(mps)
1293       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1294     else
1295       snprintf(buf, MSG_SIZ, ":%s", mytc);
1296   }
1297   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1298
1299   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1300     return FALSE;
1301   }
1302
1303   if( *tc == '/' ) {
1304     /* Parse second time control */
1305     tc++;
1306
1307     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1308       return FALSE;
1309     }
1310
1311     if( tc2 == 0 ) {
1312       return FALSE;
1313     }
1314
1315     timeControl_2 = tc2 * 1000;
1316   }
1317   else {
1318     timeControl_2 = 0;
1319   }
1320
1321   if( tc1 == 0 ) {
1322     return FALSE;
1323   }
1324
1325   timeControl = tc1 * 1000;
1326
1327   if (ti >= 0) {
1328     timeIncrement = ti * 1000;  /* convert to ms */
1329     movesPerSession = 0;
1330   } else {
1331     timeIncrement = 0;
1332     movesPerSession = mps;
1333   }
1334   return TRUE;
1335 }
1336
1337 void
1338 InitBackEnd2 ()
1339 {
1340     if (appData.debugMode) {
1341         fprintf(debugFP, "%s\n", programVersion);
1342     }
1343     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1344
1345     set_cont_sequence(appData.wrapContSeq);
1346     if (appData.matchGames > 0) {
1347         appData.matchMode = TRUE;
1348     } else if (appData.matchMode) {
1349         appData.matchGames = 1;
1350     }
1351     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1352         appData.matchGames = appData.sameColorGames;
1353     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1354         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1355         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1356     }
1357     Reset(TRUE, FALSE);
1358     if (appData.noChessProgram || first.protocolVersion == 1) {
1359       InitBackEnd3();
1360     } else {
1361       /* kludge: allow timeout for initial "feature" commands */
1362       FreezeUI();
1363       DisplayMessage("", _("Starting chess program"));
1364       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1365     }
1366 }
1367
1368 int
1369 CalculateIndex (int index, int gameNr)
1370 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1371     int res;
1372     if(index > 0) return index; // fixed nmber
1373     if(index == 0) return 1;
1374     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1375     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1376     return res;
1377 }
1378
1379 int
1380 LoadGameOrPosition (int gameNr)
1381 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1382     if (*appData.loadGameFile != NULLCHAR) {
1383         if (!LoadGameFromFile(appData.loadGameFile,
1384                 CalculateIndex(appData.loadGameIndex, gameNr),
1385                               appData.loadGameFile, FALSE)) {
1386             DisplayFatalError(_("Bad game file"), 0, 1);
1387             return 0;
1388         }
1389     } else if (*appData.loadPositionFile != NULLCHAR) {
1390         if (!LoadPositionFromFile(appData.loadPositionFile,
1391                 CalculateIndex(appData.loadPositionIndex, gameNr),
1392                                   appData.loadPositionFile)) {
1393             DisplayFatalError(_("Bad position file"), 0, 1);
1394             return 0;
1395         }
1396     }
1397     return 1;
1398 }
1399
1400 void
1401 ReserveGame (int gameNr, char resChar)
1402 {
1403     FILE *tf = fopen(appData.tourneyFile, "r+");
1404     char *p, *q, c, buf[MSG_SIZ];
1405     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1406     safeStrCpy(buf, lastMsg, MSG_SIZ);
1407     DisplayMessage(_("Pick new game"), "");
1408     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1409     ParseArgsFromFile(tf);
1410     p = q = appData.results;
1411     if(appData.debugMode) {
1412       char *r = appData.participants;
1413       fprintf(debugFP, "results = '%s'\n", p);
1414       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1415       fprintf(debugFP, "\n");
1416     }
1417     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1418     nextGame = q - p;
1419     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1420     safeStrCpy(q, p, strlen(p) + 2);
1421     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1422     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1424         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1425         q[nextGame] = '*';
1426     }
1427     fseek(tf, -(strlen(p)+4), SEEK_END);
1428     c = fgetc(tf);
1429     if(c != '"') // depending on DOS or Unix line endings we can be one off
1430          fseek(tf, -(strlen(p)+2), SEEK_END);
1431     else fseek(tf, -(strlen(p)+3), SEEK_END);
1432     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1433     DisplayMessage(buf, "");
1434     free(p); appData.results = q;
1435     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1436        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1437       int round = appData.defaultMatchGames * appData.tourneyType;
1438       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1439          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1440         UnloadEngine(&first);  // next game belongs to other pairing;
1441         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1442     }
1443     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1444 }
1445
1446 void
1447 MatchEvent (int mode)
1448 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1449         int dummy;
1450         if(matchMode) { // already in match mode: switch it off
1451             abortMatch = TRUE;
1452             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1453             return;
1454         }
1455 //      if(gameMode != BeginningOfGame) {
1456 //          DisplayError(_("You can only start a match from the initial position."), 0);
1457 //          return;
1458 //      }
1459         abortMatch = FALSE;
1460         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1461         /* Set up machine vs. machine match */
1462         nextGame = 0;
1463         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1464         if(appData.tourneyFile[0]) {
1465             ReserveGame(-1, 0);
1466             if(nextGame > appData.matchGames) {
1467                 char buf[MSG_SIZ];
1468                 if(strchr(appData.results, '*') == NULL) {
1469                     FILE *f;
1470                     appData.tourneyCycles++;
1471                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1472                         fclose(f);
1473                         NextTourneyGame(-1, &dummy);
1474                         ReserveGame(-1, 0);
1475                         if(nextGame <= appData.matchGames) {
1476                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1477                             matchMode = mode;
1478                             ScheduleDelayedEvent(NextMatchGame, 10000);
1479                             return;
1480                         }
1481                     }
1482                 }
1483                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1484                 DisplayError(buf, 0);
1485                 appData.tourneyFile[0] = 0;
1486                 return;
1487             }
1488         } else
1489         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1490             DisplayFatalError(_("Can't have a match with no chess programs"),
1491                               0, 2);
1492             return;
1493         }
1494         matchMode = mode;
1495         matchGame = roundNr = 1;
1496         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1497         NextMatchGame();
1498 }
1499
1500 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1501
1502 void
1503 InitBackEnd3 P((void))
1504 {
1505     GameMode initialMode;
1506     char buf[MSG_SIZ];
1507     int err, len;
1508
1509     InitChessProgram(&first, startedFromSetupPosition);
1510
1511     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1512         free(programVersion);
1513         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1514         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1515         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1516     }
1517
1518     if (appData.icsActive) {
1519 #ifdef WIN32
1520         /* [DM] Make a console window if needed [HGM] merged ifs */
1521         ConsoleCreate();
1522 #endif
1523         err = establish();
1524         if (err != 0)
1525           {
1526             if (*appData.icsCommPort != NULLCHAR)
1527               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1528                              appData.icsCommPort);
1529             else
1530               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1531                         appData.icsHost, appData.icsPort);
1532
1533             if( (len >= MSG_SIZ) && appData.debugMode )
1534               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1535
1536             DisplayFatalError(buf, err, 1);
1537             return;
1538         }
1539         SetICSMode();
1540         telnetISR =
1541           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1542         fromUserISR =
1543           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1544         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1545             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1546     } else if (appData.noChessProgram) {
1547         SetNCPMode();
1548     } else {
1549         SetGNUMode();
1550     }
1551
1552     if (*appData.cmailGameName != NULLCHAR) {
1553         SetCmailMode();
1554         OpenLoopback(&cmailPR);
1555         cmailISR =
1556           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1557     }
1558
1559     ThawUI();
1560     DisplayMessage("", "");
1561     if (StrCaseCmp(appData.initialMode, "") == 0) {
1562       initialMode = BeginningOfGame;
1563       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1564         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1565         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1566         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1567         ModeHighlight();
1568       }
1569     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1570       initialMode = TwoMachinesPlay;
1571     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1572       initialMode = AnalyzeFile;
1573     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1574       initialMode = AnalyzeMode;
1575     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1576       initialMode = MachinePlaysWhite;
1577     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1578       initialMode = MachinePlaysBlack;
1579     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1580       initialMode = EditGame;
1581     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1582       initialMode = EditPosition;
1583     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1584       initialMode = Training;
1585     } else {
1586       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1587       if( (len >= MSG_SIZ) && appData.debugMode )
1588         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1589
1590       DisplayFatalError(buf, 0, 2);
1591       return;
1592     }
1593
1594     if (appData.matchMode) {
1595         if(appData.tourneyFile[0]) { // start tourney from command line
1596             FILE *f;
1597             if(f = fopen(appData.tourneyFile, "r")) {
1598                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1599                 fclose(f);
1600                 appData.clockMode = TRUE;
1601                 SetGNUMode();
1602             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1603         }
1604         MatchEvent(TRUE);
1605     } else if (*appData.cmailGameName != NULLCHAR) {
1606         /* Set up cmail mode */
1607         ReloadCmailMsgEvent(TRUE);
1608     } else {
1609         /* Set up other modes */
1610         if (initialMode == AnalyzeFile) {
1611           if (*appData.loadGameFile == NULLCHAR) {
1612             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1613             return;
1614           }
1615         }
1616         if (*appData.loadGameFile != NULLCHAR) {
1617             (void) LoadGameFromFile(appData.loadGameFile,
1618                                     appData.loadGameIndex,
1619                                     appData.loadGameFile, TRUE);
1620         } else if (*appData.loadPositionFile != NULLCHAR) {
1621             (void) LoadPositionFromFile(appData.loadPositionFile,
1622                                         appData.loadPositionIndex,
1623                                         appData.loadPositionFile);
1624             /* [HGM] try to make self-starting even after FEN load */
1625             /* to allow automatic setup of fairy variants with wtm */
1626             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1627                 gameMode = BeginningOfGame;
1628                 setboardSpoiledMachineBlack = 1;
1629             }
1630             /* [HGM] loadPos: make that every new game uses the setup */
1631             /* from file as long as we do not switch variant          */
1632             if(!blackPlaysFirst) {
1633                 startedFromPositionFile = TRUE;
1634                 CopyBoard(filePosition, boards[0]);
1635             }
1636         }
1637         if (initialMode == AnalyzeMode) {
1638           if (appData.noChessProgram) {
1639             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1644             return;
1645           }
1646           AnalyzeModeEvent();
1647         } else if (initialMode == AnalyzeFile) {
1648           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1649           ShowThinkingEvent();
1650           AnalyzeFileEvent();
1651           AnalysisPeriodicEvent(1);
1652         } else if (initialMode == MachinePlaysWhite) {
1653           if (appData.noChessProgram) {
1654             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1655                               0, 2);
1656             return;
1657           }
1658           if (appData.icsActive) {
1659             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1660                               0, 2);
1661             return;
1662           }
1663           MachineWhiteEvent();
1664         } else if (initialMode == MachinePlaysBlack) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           MachineBlackEvent();
1676         } else if (initialMode == TwoMachinesPlay) {
1677           if (appData.noChessProgram) {
1678             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1679                               0, 2);
1680             return;
1681           }
1682           if (appData.icsActive) {
1683             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1684                               0, 2);
1685             return;
1686           }
1687           TwoMachinesEvent();
1688         } else if (initialMode == EditGame) {
1689           EditGameEvent();
1690         } else if (initialMode == EditPosition) {
1691           EditPositionEvent();
1692         } else if (initialMode == Training) {
1693           if (*appData.loadGameFile == NULLCHAR) {
1694             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1695             return;
1696           }
1697           TrainingEvent();
1698         }
1699     }
1700 }
1701
1702 void
1703 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1704 {
1705     DisplayBook(current+1);
1706
1707     MoveHistorySet( movelist, first, last, current, pvInfoList );
1708
1709     EvalGraphSet( first, last, current, pvInfoList );
1710
1711     MakeEngineOutputTitle();
1712 }
1713
1714 /*
1715  * Establish will establish a contact to a remote host.port.
1716  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1717  *  used to talk to the host.
1718  * Returns 0 if okay, error code if not.
1719  */
1720 int
1721 establish ()
1722 {
1723     char buf[MSG_SIZ];
1724
1725     if (*appData.icsCommPort != NULLCHAR) {
1726         /* Talk to the host through a serial comm port */
1727         return OpenCommPort(appData.icsCommPort, &icsPR);
1728
1729     } else if (*appData.gateway != NULLCHAR) {
1730         if (*appData.remoteShell == NULLCHAR) {
1731             /* Use the rcmd protocol to run telnet program on a gateway host */
1732             snprintf(buf, sizeof(buf), "%s %s %s",
1733                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1734             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1735
1736         } else {
1737             /* Use the rsh program to run telnet program on a gateway host */
1738             if (*appData.remoteUser == NULLCHAR) {
1739                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1740                         appData.gateway, appData.telnetProgram,
1741                         appData.icsHost, appData.icsPort);
1742             } else {
1743                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1744                         appData.remoteShell, appData.gateway,
1745                         appData.remoteUser, appData.telnetProgram,
1746                         appData.icsHost, appData.icsPort);
1747             }
1748             return StartChildProcess(buf, "", &icsPR);
1749
1750         }
1751     } else if (appData.useTelnet) {
1752         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1753
1754     } else {
1755         /* TCP socket interface differs somewhat between
1756            Unix and NT; handle details in the front end.
1757            */
1758         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1759     }
1760 }
1761
1762 void
1763 EscapeExpand (char *p, char *q)
1764 {       // [HGM] initstring: routine to shape up string arguments
1765         while(*p++ = *q++) if(p[-1] == '\\')
1766             switch(*q++) {
1767                 case 'n': p[-1] = '\n'; break;
1768                 case 'r': p[-1] = '\r'; break;
1769                 case 't': p[-1] = '\t'; break;
1770                 case '\\': p[-1] = '\\'; break;
1771                 case 0: *p = 0; return;
1772                 default: p[-1] = q[-1]; break;
1773             }
1774 }
1775
1776 void
1777 show_bytes (FILE *fp, char *buf, int count)
1778 {
1779     while (count--) {
1780         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1781             fprintf(fp, "\\%03o", *buf & 0xff);
1782         } else {
1783             putc(*buf, fp);
1784         }
1785         buf++;
1786     }
1787     fflush(fp);
1788 }
1789
1790 /* Returns an errno value */
1791 int
1792 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1793 {
1794     char buf[8192], *p, *q, *buflim;
1795     int left, newcount, outcount;
1796
1797     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1798         *appData.gateway != NULLCHAR) {
1799         if (appData.debugMode) {
1800             fprintf(debugFP, ">ICS: ");
1801             show_bytes(debugFP, message, count);
1802             fprintf(debugFP, "\n");
1803         }
1804         return OutputToProcess(pr, message, count, outError);
1805     }
1806
1807     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1808     p = message;
1809     q = buf;
1810     left = count;
1811     newcount = 0;
1812     while (left) {
1813         if (q >= buflim) {
1814             if (appData.debugMode) {
1815                 fprintf(debugFP, ">ICS: ");
1816                 show_bytes(debugFP, buf, newcount);
1817                 fprintf(debugFP, "\n");
1818             }
1819             outcount = OutputToProcess(pr, buf, newcount, outError);
1820             if (outcount < newcount) return -1; /* to be sure */
1821             q = buf;
1822             newcount = 0;
1823         }
1824         if (*p == '\n') {
1825             *q++ = '\r';
1826             newcount++;
1827         } else if (((unsigned char) *p) == TN_IAC) {
1828             *q++ = (char) TN_IAC;
1829             newcount ++;
1830         }
1831         *q++ = *p++;
1832         newcount++;
1833         left--;
1834     }
1835     if (appData.debugMode) {
1836         fprintf(debugFP, ">ICS: ");
1837         show_bytes(debugFP, buf, newcount);
1838         fprintf(debugFP, "\n");
1839     }
1840     outcount = OutputToProcess(pr, buf, newcount, outError);
1841     if (outcount < newcount) return -1; /* to be sure */
1842     return count;
1843 }
1844
1845 void
1846 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1847 {
1848     int outError, outCount;
1849     static int gotEof = 0;
1850     static FILE *ini;
1851
1852     /* Pass data read from player on to ICS */
1853     if (count > 0) {
1854         gotEof = 0;
1855         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1856         if (outCount < count) {
1857             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1858         }
1859         if(have_sent_ICS_logon == 2) {
1860           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1861             fprintf(ini, "%s", message);
1862             have_sent_ICS_logon = 3;
1863           } else
1864             have_sent_ICS_logon = 1;
1865         } else if(have_sent_ICS_logon == 3) {
1866             fprintf(ini, "%s", message);
1867             fclose(ini);
1868           have_sent_ICS_logon = 1;
1869         }
1870     } else if (count < 0) {
1871         RemoveInputSource(isr);
1872         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1873     } else if (gotEof++ > 0) {
1874         RemoveInputSource(isr);
1875         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1876     }
1877 }
1878
1879 void
1880 KeepAlive ()
1881 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1882     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1883     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1884     SendToICS("date\n");
1885     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1886 }
1887
1888 /* added routine for printf style output to ics */
1889 void
1890 ics_printf (char *format, ...)
1891 {
1892     char buffer[MSG_SIZ];
1893     va_list args;
1894
1895     va_start(args, format);
1896     vsnprintf(buffer, sizeof(buffer), format, args);
1897     buffer[sizeof(buffer)-1] = '\0';
1898     SendToICS(buffer);
1899     va_end(args);
1900 }
1901
1902 void
1903 SendToICS (char *s)
1904 {
1905     int count, outCount, outError;
1906
1907     if (icsPR == NoProc) return;
1908
1909     count = strlen(s);
1910     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1911     if (outCount < count) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 /* This is used for sending logon scripts to the ICS. Sending
1917    without a delay causes problems when using timestamp on ICC
1918    (at least on my machine). */
1919 void
1920 SendToICSDelayed (char *s, long msdelay)
1921 {
1922     int count, outCount, outError;
1923
1924     if (icsPR == NoProc) return;
1925
1926     count = strlen(s);
1927     if (appData.debugMode) {
1928         fprintf(debugFP, ">ICS: ");
1929         show_bytes(debugFP, s, count);
1930         fprintf(debugFP, "\n");
1931     }
1932     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1933                                       msdelay);
1934     if (outCount < count) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939
1940 /* Remove all highlighting escape sequences in s
1941    Also deletes any suffix starting with '('
1942    */
1943 char *
1944 StripHighlightAndTitle (char *s)
1945 {
1946     static char retbuf[MSG_SIZ];
1947     char *p = retbuf;
1948
1949     while (*s != NULLCHAR) {
1950         while (*s == '\033') {
1951             while (*s != NULLCHAR && !isalpha(*s)) s++;
1952             if (*s != NULLCHAR) s++;
1953         }
1954         while (*s != NULLCHAR && *s != '\033') {
1955             if (*s == '(' || *s == '[') {
1956                 *p = NULLCHAR;
1957                 return retbuf;
1958             }
1959             *p++ = *s++;
1960         }
1961     }
1962     *p = NULLCHAR;
1963     return retbuf;
1964 }
1965
1966 /* Remove all highlighting escape sequences in s */
1967 char *
1968 StripHighlight (char *s)
1969 {
1970     static char retbuf[MSG_SIZ];
1971     char *p = retbuf;
1972
1973     while (*s != NULLCHAR) {
1974         while (*s == '\033') {
1975             while (*s != NULLCHAR && !isalpha(*s)) s++;
1976             if (*s != NULLCHAR) s++;
1977         }
1978         while (*s != NULLCHAR && *s != '\033') {
1979             *p++ = *s++;
1980         }
1981     }
1982     *p = NULLCHAR;
1983     return retbuf;
1984 }
1985
1986 char *variantNames[] = VARIANT_NAMES;
1987 char *
1988 VariantName (VariantClass v)
1989 {
1990     return variantNames[v];
1991 }
1992
1993
1994 /* Identify a variant from the strings the chess servers use or the
1995    PGN Variant tag names we use. */
1996 VariantClass
1997 StringToVariant (char *e)
1998 {
1999     char *p;
2000     int wnum = -1;
2001     VariantClass v = VariantNormal;
2002     int i, found = FALSE;
2003     char buf[MSG_SIZ];
2004     int len;
2005
2006     if (!e) return v;
2007
2008     /* [HGM] skip over optional board-size prefixes */
2009     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2010         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2011         while( *e++ != '_');
2012     }
2013
2014     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2015         v = VariantNormal;
2016         found = TRUE;
2017     } else
2018     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2019       if (StrCaseStr(e, variantNames[i])) {
2020         v = (VariantClass) i;
2021         found = TRUE;
2022         break;
2023       }
2024     }
2025
2026     if (!found) {
2027       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2028           || StrCaseStr(e, "wild/fr")
2029           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2030         v = VariantFischeRandom;
2031       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2032                  (i = 1, p = StrCaseStr(e, "w"))) {
2033         p += i;
2034         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2035         if (isdigit(*p)) {
2036           wnum = atoi(p);
2037         } else {
2038           wnum = -1;
2039         }
2040         switch (wnum) {
2041         case 0: /* FICS only, actually */
2042         case 1:
2043           /* Castling legal even if K starts on d-file */
2044           v = VariantWildCastle;
2045           break;
2046         case 2:
2047         case 3:
2048         case 4:
2049           /* Castling illegal even if K & R happen to start in
2050              normal positions. */
2051           v = VariantNoCastle;
2052           break;
2053         case 5:
2054         case 7:
2055         case 8:
2056         case 10:
2057         case 11:
2058         case 12:
2059         case 13:
2060         case 14:
2061         case 15:
2062         case 18:
2063         case 19:
2064           /* Castling legal iff K & R start in normal positions */
2065           v = VariantNormal;
2066           break;
2067         case 6:
2068         case 20:
2069         case 21:
2070           /* Special wilds for position setup; unclear what to do here */
2071           v = VariantLoadable;
2072           break;
2073         case 9:
2074           /* Bizarre ICC game */
2075           v = VariantTwoKings;
2076           break;
2077         case 16:
2078           v = VariantKriegspiel;
2079           break;
2080         case 17:
2081           v = VariantLosers;
2082           break;
2083         case 22:
2084           v = VariantFischeRandom;
2085           break;
2086         case 23:
2087           v = VariantCrazyhouse;
2088           break;
2089         case 24:
2090           v = VariantBughouse;
2091           break;
2092         case 25:
2093           v = Variant3Check;
2094           break;
2095         case 26:
2096           /* Not quite the same as FICS suicide! */
2097           v = VariantGiveaway;
2098           break;
2099         case 27:
2100           v = VariantAtomic;
2101           break;
2102         case 28:
2103           v = VariantShatranj;
2104           break;
2105
2106         /* Temporary names for future ICC types.  The name *will* change in
2107            the next xboard/WinBoard release after ICC defines it. */
2108         case 29:
2109           v = Variant29;
2110           break;
2111         case 30:
2112           v = Variant30;
2113           break;
2114         case 31:
2115           v = Variant31;
2116           break;
2117         case 32:
2118           v = Variant32;
2119           break;
2120         case 33:
2121           v = Variant33;
2122           break;
2123         case 34:
2124           v = Variant34;
2125           break;
2126         case 35:
2127           v = Variant35;
2128           break;
2129         case 36:
2130           v = Variant36;
2131           break;
2132         case 37:
2133           v = VariantShogi;
2134           break;
2135         case 38:
2136           v = VariantXiangqi;
2137           break;
2138         case 39:
2139           v = VariantCourier;
2140           break;
2141         case 40:
2142           v = VariantGothic;
2143           break;
2144         case 41:
2145           v = VariantCapablanca;
2146           break;
2147         case 42:
2148           v = VariantKnightmate;
2149           break;
2150         case 43:
2151           v = VariantFairy;
2152           break;
2153         case 44:
2154           v = VariantCylinder;
2155           break;
2156         case 45:
2157           v = VariantFalcon;
2158           break;
2159         case 46:
2160           v = VariantCapaRandom;
2161           break;
2162         case 47:
2163           v = VariantBerolina;
2164           break;
2165         case 48:
2166           v = VariantJanus;
2167           break;
2168         case 49:
2169           v = VariantSuper;
2170           break;
2171         case 50:
2172           v = VariantGreat;
2173           break;
2174         case -1:
2175           /* Found "wild" or "w" in the string but no number;
2176              must assume it's normal chess. */
2177           v = VariantNormal;
2178           break;
2179         default:
2180           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2181           if( (len >= MSG_SIZ) && appData.debugMode )
2182             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2183
2184           DisplayError(buf, 0);
2185           v = VariantUnknown;
2186           break;
2187         }
2188       }
2189     }
2190     if (appData.debugMode) {
2191       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2192               e, wnum, VariantName(v));
2193     }
2194     return v;
2195 }
2196
2197 static int leftover_start = 0, leftover_len = 0;
2198 char star_match[STAR_MATCH_N][MSG_SIZ];
2199
2200 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2201    advance *index beyond it, and set leftover_start to the new value of
2202    *index; else return FALSE.  If pattern contains the character '*', it
2203    matches any sequence of characters not containing '\r', '\n', or the
2204    character following the '*' (if any), and the matched sequence(s) are
2205    copied into star_match.
2206    */
2207 int
2208 looking_at ( char *buf, int *index, char *pattern)
2209 {
2210     char *bufp = &buf[*index], *patternp = pattern;
2211     int star_count = 0;
2212     char *matchp = star_match[0];
2213
2214     for (;;) {
2215         if (*patternp == NULLCHAR) {
2216             *index = leftover_start = bufp - buf;
2217             *matchp = NULLCHAR;
2218             return TRUE;
2219         }
2220         if (*bufp == NULLCHAR) return FALSE;
2221         if (*patternp == '*') {
2222             if (*bufp == *(patternp + 1)) {
2223                 *matchp = NULLCHAR;
2224                 matchp = star_match[++star_count];
2225                 patternp += 2;
2226                 bufp++;
2227                 continue;
2228             } else if (*bufp == '\n' || *bufp == '\r') {
2229                 patternp++;
2230                 if (*patternp == NULLCHAR)
2231                   continue;
2232                 else
2233                   return FALSE;
2234             } else {
2235                 *matchp++ = *bufp++;
2236                 continue;
2237             }
2238         }
2239         if (*patternp != *bufp) return FALSE;
2240         patternp++;
2241         bufp++;
2242     }
2243 }
2244
2245 void
2246 SendToPlayer (char *data, int length)
2247 {
2248     int error, outCount;
2249     outCount = OutputToProcess(NoProc, data, length, &error);
2250     if (outCount < length) {
2251         DisplayFatalError(_("Error writing to display"), error, 1);
2252     }
2253 }
2254
2255 void
2256 PackHolding (char packed[], char *holding)
2257 {
2258     char *p = holding;
2259     char *q = packed;
2260     int runlength = 0;
2261     int curr = 9999;
2262     do {
2263         if (*p == curr) {
2264             runlength++;
2265         } else {
2266             switch (runlength) {
2267               case 0:
2268                 break;
2269               case 1:
2270                 *q++ = curr;
2271                 break;
2272               case 2:
2273                 *q++ = curr;
2274                 *q++ = curr;
2275                 break;
2276               default:
2277                 sprintf(q, "%d", runlength);
2278                 while (*q) q++;
2279                 *q++ = curr;
2280                 break;
2281             }
2282             runlength = 1;
2283             curr = *p;
2284         }
2285     } while (*p++);
2286     *q = NULLCHAR;
2287 }
2288
2289 /* Telnet protocol requests from the front end */
2290 void
2291 TelnetRequest (unsigned char ddww, unsigned char option)
2292 {
2293     unsigned char msg[3];
2294     int outCount, outError;
2295
2296     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2297
2298     if (appData.debugMode) {
2299         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2300         switch (ddww) {
2301           case TN_DO:
2302             ddwwStr = "DO";
2303             break;
2304           case TN_DONT:
2305             ddwwStr = "DONT";
2306             break;
2307           case TN_WILL:
2308             ddwwStr = "WILL";
2309             break;
2310           case TN_WONT:
2311             ddwwStr = "WONT";
2312             break;
2313           default:
2314             ddwwStr = buf1;
2315             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2316             break;
2317         }
2318         switch (option) {
2319           case TN_ECHO:
2320             optionStr = "ECHO";
2321             break;
2322           default:
2323             optionStr = buf2;
2324             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2325             break;
2326         }
2327         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2328     }
2329     msg[0] = TN_IAC;
2330     msg[1] = ddww;
2331     msg[2] = option;
2332     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2333     if (outCount < 3) {
2334         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2335     }
2336 }
2337
2338 void
2339 DoEcho ()
2340 {
2341     if (!appData.icsActive) return;
2342     TelnetRequest(TN_DO, TN_ECHO);
2343 }
2344
2345 void
2346 DontEcho ()
2347 {
2348     if (!appData.icsActive) return;
2349     TelnetRequest(TN_DONT, TN_ECHO);
2350 }
2351
2352 void
2353 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2354 {
2355     /* put the holdings sent to us by the server on the board holdings area */
2356     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2357     char p;
2358     ChessSquare piece;
2359
2360     if(gameInfo.holdingsWidth < 2)  return;
2361     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2362         return; // prevent overwriting by pre-board holdings
2363
2364     if( (int)lowestPiece >= BlackPawn ) {
2365         holdingsColumn = 0;
2366         countsColumn = 1;
2367         holdingsStartRow = BOARD_HEIGHT-1;
2368         direction = -1;
2369     } else {
2370         holdingsColumn = BOARD_WIDTH-1;
2371         countsColumn = BOARD_WIDTH-2;
2372         holdingsStartRow = 0;
2373         direction = 1;
2374     }
2375
2376     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2377         board[i][holdingsColumn] = EmptySquare;
2378         board[i][countsColumn]   = (ChessSquare) 0;
2379     }
2380     while( (p=*holdings++) != NULLCHAR ) {
2381         piece = CharToPiece( ToUpper(p) );
2382         if(piece == EmptySquare) continue;
2383         /*j = (int) piece - (int) WhitePawn;*/
2384         j = PieceToNumber(piece);
2385         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2386         if(j < 0) continue;               /* should not happen */
2387         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2388         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2389         board[holdingsStartRow+j*direction][countsColumn]++;
2390     }
2391 }
2392
2393
2394 void
2395 VariantSwitch (Board board, VariantClass newVariant)
2396 {
2397    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2398    static Board oldBoard;
2399
2400    startedFromPositionFile = FALSE;
2401    if(gameInfo.variant == newVariant) return;
2402
2403    /* [HGM] This routine is called each time an assignment is made to
2404     * gameInfo.variant during a game, to make sure the board sizes
2405     * are set to match the new variant. If that means adding or deleting
2406     * holdings, we shift the playing board accordingly
2407     * This kludge is needed because in ICS observe mode, we get boards
2408     * of an ongoing game without knowing the variant, and learn about the
2409     * latter only later. This can be because of the move list we requested,
2410     * in which case the game history is refilled from the beginning anyway,
2411     * but also when receiving holdings of a crazyhouse game. In the latter
2412     * case we want to add those holdings to the already received position.
2413     */
2414
2415
2416    if (appData.debugMode) {
2417      fprintf(debugFP, "Switch board from %s to %s\n",
2418              VariantName(gameInfo.variant), VariantName(newVariant));
2419      setbuf(debugFP, NULL);
2420    }
2421    shuffleOpenings = 0;       /* [HGM] shuffle */
2422    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2423    switch(newVariant)
2424      {
2425      case VariantShogi:
2426        newWidth = 9;  newHeight = 9;
2427        gameInfo.holdingsSize = 7;
2428      case VariantBughouse:
2429      case VariantCrazyhouse:
2430        newHoldingsWidth = 2; break;
2431      case VariantGreat:
2432        newWidth = 10;
2433      case VariantSuper:
2434        newHoldingsWidth = 2;
2435        gameInfo.holdingsSize = 8;
2436        break;
2437      case VariantGothic:
2438      case VariantCapablanca:
2439      case VariantCapaRandom:
2440        newWidth = 10;
2441      default:
2442        newHoldingsWidth = gameInfo.holdingsSize = 0;
2443      };
2444
2445    if(newWidth  != gameInfo.boardWidth  ||
2446       newHeight != gameInfo.boardHeight ||
2447       newHoldingsWidth != gameInfo.holdingsWidth ) {
2448
2449      /* shift position to new playing area, if needed */
2450      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2451        for(i=0; i<BOARD_HEIGHT; i++)
2452          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2453            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2454              board[i][j];
2455        for(i=0; i<newHeight; i++) {
2456          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2457          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2458        }
2459      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2460        for(i=0; i<BOARD_HEIGHT; i++)
2461          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2462            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2463              board[i][j];
2464      }
2465      board[HOLDINGS_SET] = 0;
2466      gameInfo.boardWidth  = newWidth;
2467      gameInfo.boardHeight = newHeight;
2468      gameInfo.holdingsWidth = newHoldingsWidth;
2469      gameInfo.variant = newVariant;
2470      InitDrawingSizes(-2, 0);
2471    } else gameInfo.variant = newVariant;
2472    CopyBoard(oldBoard, board);   // remember correctly formatted board
2473      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2474    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2475 }
2476
2477 static int loggedOn = FALSE;
2478
2479 /*-- Game start info cache: --*/
2480 int gs_gamenum;
2481 char gs_kind[MSG_SIZ];
2482 static char player1Name[128] = "";
2483 static char player2Name[128] = "";
2484 static char cont_seq[] = "\n\\   ";
2485 static int player1Rating = -1;
2486 static int player2Rating = -1;
2487 /*----------------------------*/
2488
2489 ColorClass curColor = ColorNormal;
2490 int suppressKibitz = 0;
2491
2492 // [HGM] seekgraph
2493 Boolean soughtPending = FALSE;
2494 Boolean seekGraphUp;
2495 #define MAX_SEEK_ADS 200
2496 #define SQUARE 0x80
2497 char *seekAdList[MAX_SEEK_ADS];
2498 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2499 float tcList[MAX_SEEK_ADS];
2500 char colorList[MAX_SEEK_ADS];
2501 int nrOfSeekAds = 0;
2502 int minRating = 1010, maxRating = 2800;
2503 int hMargin = 10, vMargin = 20, h, w;
2504 extern int squareSize, lineGap;
2505
2506 void
2507 PlotSeekAd (int i)
2508 {
2509         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2510         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2511         if(r < minRating+100 && r >=0 ) r = minRating+100;
2512         if(r > maxRating) r = maxRating;
2513         if(tc < 1.f) tc = 1.f;
2514         if(tc > 95.f) tc = 95.f;
2515         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2516         y = ((double)r - minRating)/(maxRating - minRating)
2517             * (h-vMargin-squareSize/8-1) + vMargin;
2518         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2519         if(strstr(seekAdList[i], " u ")) color = 1;
2520         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2521            !strstr(seekAdList[i], "bullet") &&
2522            !strstr(seekAdList[i], "blitz") &&
2523            !strstr(seekAdList[i], "standard") ) color = 2;
2524         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2525         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2526 }
2527
2528 void
2529 PlotSingleSeekAd (int i)
2530 {
2531         PlotSeekAd(i);
2532 }
2533
2534 void
2535 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2536 {
2537         char buf[MSG_SIZ], *ext = "";
2538         VariantClass v = StringToVariant(type);
2539         if(strstr(type, "wild")) {
2540             ext = type + 4; // append wild number
2541             if(v == VariantFischeRandom) type = "chess960"; else
2542             if(v == VariantLoadable) type = "setup"; else
2543             type = VariantName(v);
2544         }
2545         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2546         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2547             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2548             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2549             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2550             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2551             seekNrList[nrOfSeekAds] = nr;
2552             zList[nrOfSeekAds] = 0;
2553             seekAdList[nrOfSeekAds++] = StrSave(buf);
2554             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2555         }
2556 }
2557
2558 void
2559 EraseSeekDot (int i)
2560 {
2561     int x = xList[i], y = yList[i], d=squareSize/4, k;
2562     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2563     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2564     // now replot every dot that overlapped
2565     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2566         int xx = xList[k], yy = yList[k];
2567         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2568             DrawSeekDot(xx, yy, colorList[k]);
2569     }
2570 }
2571
2572 void
2573 RemoveSeekAd (int nr)
2574 {
2575         int i;
2576         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2577             EraseSeekDot(i);
2578             if(seekAdList[i]) free(seekAdList[i]);
2579             seekAdList[i] = seekAdList[--nrOfSeekAds];
2580             seekNrList[i] = seekNrList[nrOfSeekAds];
2581             ratingList[i] = ratingList[nrOfSeekAds];
2582             colorList[i]  = colorList[nrOfSeekAds];
2583             tcList[i] = tcList[nrOfSeekAds];
2584             xList[i]  = xList[nrOfSeekAds];
2585             yList[i]  = yList[nrOfSeekAds];
2586             zList[i]  = zList[nrOfSeekAds];
2587             seekAdList[nrOfSeekAds] = NULL;
2588             break;
2589         }
2590 }
2591
2592 Boolean
2593 MatchSoughtLine (char *line)
2594 {
2595     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2596     int nr, base, inc, u=0; char dummy;
2597
2598     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2599        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2600        (u=1) &&
2601        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2602         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2603         // match: compact and save the line
2604         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2605         return TRUE;
2606     }
2607     return FALSE;
2608 }
2609
2610 int
2611 DrawSeekGraph ()
2612 {
2613     int i;
2614     if(!seekGraphUp) return FALSE;
2615     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2616     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2617
2618     DrawSeekBackground(0, 0, w, h);
2619     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2620     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2621     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2622         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2623         yy = h-1-yy;
2624         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2625         if(i%500 == 0) {
2626             char buf[MSG_SIZ];
2627             snprintf(buf, MSG_SIZ, "%d", i);
2628             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2629         }
2630     }
2631     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2632     for(i=1; i<100; i+=(i<10?1:5)) {
2633         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2634         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2635         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2636             char buf[MSG_SIZ];
2637             snprintf(buf, MSG_SIZ, "%d", i);
2638             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2639         }
2640     }
2641     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2642     return TRUE;
2643 }
2644
2645 int
2646 SeekGraphClick (ClickType click, int x, int y, int moving)
2647 {
2648     static int lastDown = 0, displayed = 0, lastSecond;
2649     if(y < 0) return FALSE;
2650     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2651         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2652         if(!seekGraphUp) return FALSE;
2653         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2654         DrawPosition(TRUE, NULL);
2655         return TRUE;
2656     }
2657     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2658         if(click == Release || moving) return FALSE;
2659         nrOfSeekAds = 0;
2660         soughtPending = TRUE;
2661         SendToICS(ics_prefix);
2662         SendToICS("sought\n"); // should this be "sought all"?
2663     } else { // issue challenge based on clicked ad
2664         int dist = 10000; int i, closest = 0, second = 0;
2665         for(i=0; i<nrOfSeekAds; i++) {
2666             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2667             if(d < dist) { dist = d; closest = i; }
2668             second += (d - zList[i] < 120); // count in-range ads
2669             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2670         }
2671         if(dist < 120) {
2672             char buf[MSG_SIZ];
2673             second = (second > 1);
2674             if(displayed != closest || second != lastSecond) {
2675                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2676                 lastSecond = second; displayed = closest;
2677             }
2678             if(click == Press) {
2679                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2680                 lastDown = closest;
2681                 return TRUE;
2682             } // on press 'hit', only show info
2683             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2684             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2685             SendToICS(ics_prefix);
2686             SendToICS(buf);
2687             return TRUE; // let incoming board of started game pop down the graph
2688         } else if(click == Release) { // release 'miss' is ignored
2689             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2690             if(moving == 2) { // right up-click
2691                 nrOfSeekAds = 0; // refresh graph
2692                 soughtPending = TRUE;
2693                 SendToICS(ics_prefix);
2694                 SendToICS("sought\n"); // should this be "sought all"?
2695             }
2696             return TRUE;
2697         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2698         // press miss or release hit 'pop down' seek graph
2699         seekGraphUp = FALSE;
2700         DrawPosition(TRUE, NULL);
2701     }
2702     return TRUE;
2703 }
2704
2705 void
2706 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2707 {
2708 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2709 #define STARTED_NONE 0
2710 #define STARTED_MOVES 1
2711 #define STARTED_BOARD 2
2712 #define STARTED_OBSERVE 3
2713 #define STARTED_HOLDINGS 4
2714 #define STARTED_CHATTER 5
2715 #define STARTED_COMMENT 6
2716 #define STARTED_MOVES_NOHIDE 7
2717
2718     static int started = STARTED_NONE;
2719     static char parse[20000];
2720     static int parse_pos = 0;
2721     static char buf[BUF_SIZE + 1];
2722     static int firstTime = TRUE, intfSet = FALSE;
2723     static ColorClass prevColor = ColorNormal;
2724     static int savingComment = FALSE;
2725     static int cmatch = 0; // continuation sequence match
2726     char *bp;
2727     char str[MSG_SIZ];
2728     int i, oldi;
2729     int buf_len;
2730     int next_out;
2731     int tkind;
2732     int backup;    /* [DM] For zippy color lines */
2733     char *p;
2734     char talker[MSG_SIZ]; // [HGM] chat
2735     int channel;
2736
2737     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2738
2739     if (appData.debugMode) {
2740       if (!error) {
2741         fprintf(debugFP, "<ICS: ");
2742         show_bytes(debugFP, data, count);
2743         fprintf(debugFP, "\n");
2744       }
2745     }
2746
2747     if (appData.debugMode) { int f = forwardMostMove;
2748         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2749                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2750                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2751     }
2752     if (count > 0) {
2753         /* If last read ended with a partial line that we couldn't parse,
2754            prepend it to the new read and try again. */
2755         if (leftover_len > 0) {
2756             for (i=0; i<leftover_len; i++)
2757               buf[i] = buf[leftover_start + i];
2758         }
2759
2760     /* copy new characters into the buffer */
2761     bp = buf + leftover_len;
2762     buf_len=leftover_len;
2763     for (i=0; i<count; i++)
2764     {
2765         // ignore these
2766         if (data[i] == '\r')
2767             continue;
2768
2769         // join lines split by ICS?
2770         if (!appData.noJoin)
2771         {
2772             /*
2773                 Joining just consists of finding matches against the
2774                 continuation sequence, and discarding that sequence
2775                 if found instead of copying it.  So, until a match
2776                 fails, there's nothing to do since it might be the
2777                 complete sequence, and thus, something we don't want
2778                 copied.
2779             */
2780             if (data[i] == cont_seq[cmatch])
2781             {
2782                 cmatch++;
2783                 if (cmatch == strlen(cont_seq))
2784                 {
2785                     cmatch = 0; // complete match.  just reset the counter
2786
2787                     /*
2788                         it's possible for the ICS to not include the space
2789                         at the end of the last word, making our [correct]
2790                         join operation fuse two separate words.  the server
2791                         does this when the space occurs at the width setting.
2792                     */
2793                     if (!buf_len || buf[buf_len-1] != ' ')
2794                     {
2795                         *bp++ = ' ';
2796                         buf_len++;
2797                     }
2798                 }
2799                 continue;
2800             }
2801             else if (cmatch)
2802             {
2803                 /*
2804                     match failed, so we have to copy what matched before
2805                     falling through and copying this character.  In reality,
2806                     this will only ever be just the newline character, but
2807                     it doesn't hurt to be precise.
2808                 */
2809                 strncpy(bp, cont_seq, cmatch);
2810                 bp += cmatch;
2811                 buf_len += cmatch;
2812                 cmatch = 0;
2813             }
2814         }
2815
2816         // copy this char
2817         *bp++ = data[i];
2818         buf_len++;
2819     }
2820
2821         buf[buf_len] = NULLCHAR;
2822 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2823         next_out = 0;
2824         leftover_start = 0;
2825
2826         i = 0;
2827         while (i < buf_len) {
2828             /* Deal with part of the TELNET option negotiation
2829                protocol.  We refuse to do anything beyond the
2830                defaults, except that we allow the WILL ECHO option,
2831                which ICS uses to turn off password echoing when we are
2832                directly connected to it.  We reject this option
2833                if localLineEditing mode is on (always on in xboard)
2834                and we are talking to port 23, which might be a real
2835                telnet server that will try to keep WILL ECHO on permanently.
2836              */
2837             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2838                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2839                 unsigned char option;
2840                 oldi = i;
2841                 switch ((unsigned char) buf[++i]) {
2842                   case TN_WILL:
2843                     if (appData.debugMode)
2844                       fprintf(debugFP, "\n<WILL ");
2845                     switch (option = (unsigned char) buf[++i]) {
2846                       case TN_ECHO:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "ECHO ");
2849                         /* Reply only if this is a change, according
2850                            to the protocol rules. */
2851                         if (remoteEchoOption) break;
2852                         if (appData.localLineEditing &&
2853                             atoi(appData.icsPort) == TN_PORT) {
2854                             TelnetRequest(TN_DONT, TN_ECHO);
2855                         } else {
2856                             EchoOff();
2857                             TelnetRequest(TN_DO, TN_ECHO);
2858                             remoteEchoOption = TRUE;
2859                         }
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", option);
2864                         /* Whatever this is, we don't want it. */
2865                         TelnetRequest(TN_DONT, option);
2866                         break;
2867                     }
2868                     break;
2869                   case TN_WONT:
2870                     if (appData.debugMode)
2871                       fprintf(debugFP, "\n<WONT ");
2872                     switch (option = (unsigned char) buf[++i]) {
2873                       case TN_ECHO:
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "ECHO ");
2876                         /* Reply only if this is a change, according
2877                            to the protocol rules. */
2878                         if (!remoteEchoOption) break;
2879                         EchoOn();
2880                         TelnetRequest(TN_DONT, TN_ECHO);
2881                         remoteEchoOption = FALSE;
2882                         break;
2883                       default:
2884                         if (appData.debugMode)
2885                           fprintf(debugFP, "%d ", (unsigned char) option);
2886                         /* Whatever this is, it must already be turned
2887                            off, because we never agree to turn on
2888                            anything non-default, so according to the
2889                            protocol rules, we don't reply. */
2890                         break;
2891                     }
2892                     break;
2893                   case TN_DO:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<DO ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       default:
2898                         /* Whatever this is, we refuse to do it. */
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "%d ", option);
2901                         TelnetRequest(TN_WONT, option);
2902                         break;
2903                     }
2904                     break;
2905                   case TN_DONT:
2906                     if (appData.debugMode)
2907                       fprintf(debugFP, "\n<DONT ");
2908                     switch (option = (unsigned char) buf[++i]) {
2909                       default:
2910                         if (appData.debugMode)
2911                           fprintf(debugFP, "%d ", option);
2912                         /* Whatever this is, we are already not doing
2913                            it, because we never agree to do anything
2914                            non-default, so according to the protocol
2915                            rules, we don't reply. */
2916                         break;
2917                     }
2918                     break;
2919                   case TN_IAC:
2920                     if (appData.debugMode)
2921                       fprintf(debugFP, "\n<IAC ");
2922                     /* Doubled IAC; pass it through */
2923                     i--;
2924                     break;
2925                   default:
2926                     if (appData.debugMode)
2927                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2928                     /* Drop all other telnet commands on the floor */
2929                     break;
2930                 }
2931                 if (oldi > next_out)
2932                   SendToPlayer(&buf[next_out], oldi - next_out);
2933                 if (++i > next_out)
2934                   next_out = i;
2935                 continue;
2936             }
2937
2938             /* OK, this at least will *usually* work */
2939             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2940                 loggedOn = TRUE;
2941             }
2942
2943             if (loggedOn && !intfSet) {
2944                 if (ics_type == ICS_ICC) {
2945                   snprintf(str, MSG_SIZ,
2946                           "/set-quietly interface %s\n/set-quietly style 12\n",
2947                           programVersion);
2948                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2949                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2950                 } else if (ics_type == ICS_CHESSNET) {
2951                   snprintf(str, MSG_SIZ, "/style 12\n");
2952                 } else {
2953                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2954                   strcat(str, programVersion);
2955                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2956                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2957                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2958 #ifdef WIN32
2959                   strcat(str, "$iset nohighlight 1\n");
2960 #endif
2961                   strcat(str, "$iset lock 1\n$style 12\n");
2962                 }
2963                 SendToICS(str);
2964                 NotifyFrontendLogin();
2965                 intfSet = TRUE;
2966             }
2967
2968             if (started == STARTED_COMMENT) {
2969                 /* Accumulate characters in comment */
2970                 parse[parse_pos++] = buf[i];
2971                 if (buf[i] == '\n') {
2972                     parse[parse_pos] = NULLCHAR;
2973                     if(chattingPartner>=0) {
2974                         char mess[MSG_SIZ];
2975                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2976                         OutputChatMessage(chattingPartner, mess);
2977                         chattingPartner = -1;
2978                         next_out = i+1; // [HGM] suppress printing in ICS window
2979                     } else
2980                     if(!suppressKibitz) // [HGM] kibitz
2981                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2982                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2983                         int nrDigit = 0, nrAlph = 0, j;
2984                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2985                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2986                         parse[parse_pos] = NULLCHAR;
2987                         // try to be smart: if it does not look like search info, it should go to
2988                         // ICS interaction window after all, not to engine-output window.
2989                         for(j=0; j<parse_pos; j++) { // count letters and digits
2990                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2991                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2992                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2993                         }
2994                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2995                             int depth=0; float score;
2996                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2997                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2998                                 pvInfoList[forwardMostMove-1].depth = depth;
2999                                 pvInfoList[forwardMostMove-1].score = 100*score;
3000                             }
3001                             OutputKibitz(suppressKibitz, parse);
3002                         } else {
3003                             char tmp[MSG_SIZ];
3004                             if(gameMode == IcsObserving) // restore original ICS messages
3005                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3006                             else
3007                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3008                             SendToPlayer(tmp, strlen(tmp));
3009                         }
3010                         next_out = i+1; // [HGM] suppress printing in ICS window
3011                     }
3012                     started = STARTED_NONE;
3013                 } else {
3014                     /* Don't match patterns against characters in comment */
3015                     i++;
3016                     continue;
3017                 }
3018             }
3019             if (started == STARTED_CHATTER) {
3020                 if (buf[i] != '\n') {
3021                     /* Don't match patterns against characters in chatter */
3022                     i++;
3023                     continue;
3024                 }
3025                 started = STARTED_NONE;
3026                 if(suppressKibitz) next_out = i+1;
3027             }
3028
3029             /* Kludge to deal with rcmd protocol */
3030             if (firstTime && looking_at(buf, &i, "\001*")) {
3031                 DisplayFatalError(&buf[1], 0, 1);
3032                 continue;
3033             } else {
3034                 firstTime = FALSE;
3035             }
3036
3037             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3038                 ics_type = ICS_ICC;
3039                 ics_prefix = "/";
3040                 if (appData.debugMode)
3041                   fprintf(debugFP, "ics_type %d\n", ics_type);
3042                 continue;
3043             }
3044             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3045                 ics_type = ICS_FICS;
3046                 ics_prefix = "$";
3047                 if (appData.debugMode)
3048                   fprintf(debugFP, "ics_type %d\n", ics_type);
3049                 continue;
3050             }
3051             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3052                 ics_type = ICS_CHESSNET;
3053                 ics_prefix = "/";
3054                 if (appData.debugMode)
3055                   fprintf(debugFP, "ics_type %d\n", ics_type);
3056                 continue;
3057             }
3058
3059             if (!loggedOn &&
3060                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3061                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3062                  looking_at(buf, &i, "will be \"*\""))) {
3063               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3064               continue;
3065             }
3066
3067             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3068               char buf[MSG_SIZ];
3069               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3070               DisplayIcsInteractionTitle(buf);
3071               have_set_title = TRUE;
3072             }
3073
3074             /* skip finger notes */
3075             if (started == STARTED_NONE &&
3076                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3077                  (buf[i] == '1' && buf[i+1] == '0')) &&
3078                 buf[i+2] == ':' && buf[i+3] == ' ') {
3079               started = STARTED_CHATTER;
3080               i += 3;
3081               continue;
3082             }
3083
3084             oldi = i;
3085             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3086             if(appData.seekGraph) {
3087                 if(soughtPending && MatchSoughtLine(buf+i)) {
3088                     i = strstr(buf+i, "rated") - buf;
3089                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090                     next_out = leftover_start = i;
3091                     started = STARTED_CHATTER;
3092                     suppressKibitz = TRUE;
3093                     continue;
3094                 }
3095                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3096                         && looking_at(buf, &i, "* ads displayed")) {
3097                     soughtPending = FALSE;
3098                     seekGraphUp = TRUE;
3099                     DrawSeekGraph();
3100                     continue;
3101                 }
3102                 if(appData.autoRefresh) {
3103                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3104                         int s = (ics_type == ICS_ICC); // ICC format differs
3105                         if(seekGraphUp)
3106                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3107                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3108                         looking_at(buf, &i, "*% "); // eat prompt
3109                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3110                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                         next_out = i; // suppress
3112                         continue;
3113                     }
3114                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3115                         char *p = star_match[0];
3116                         while(*p) {
3117                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3118                             while(*p && *p++ != ' '); // next
3119                         }
3120                         looking_at(buf, &i, "*% "); // eat prompt
3121                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3122                         next_out = i;
3123                         continue;
3124                     }
3125                 }
3126             }
3127
3128             /* skip formula vars */
3129             if (started == STARTED_NONE &&
3130                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3131               started = STARTED_CHATTER;
3132               i += 3;
3133               continue;
3134             }
3135
3136             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3137             if (appData.autoKibitz && started == STARTED_NONE &&
3138                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3139                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3140                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3141                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3142                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3143                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3144                         suppressKibitz = TRUE;
3145                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3146                         next_out = i;
3147                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3148                                 && (gameMode == IcsPlayingWhite)) ||
3149                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3150                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3151                             started = STARTED_CHATTER; // own kibitz we simply discard
3152                         else {
3153                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3154                             parse_pos = 0; parse[0] = NULLCHAR;
3155                             savingComment = TRUE;
3156                             suppressKibitz = gameMode != IcsObserving ? 2 :
3157                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3158                         }
3159                         continue;
3160                 } else
3161                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3162                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3163                          && atoi(star_match[0])) {
3164                     // suppress the acknowledgements of our own autoKibitz
3165                     char *p;
3166                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3168                     SendToPlayer(star_match[0], strlen(star_match[0]));
3169                     if(looking_at(buf, &i, "*% ")) // eat prompt
3170                         suppressKibitz = FALSE;
3171                     next_out = i;
3172                     continue;
3173                 }
3174             } // [HGM] kibitz: end of patch
3175
3176             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3177
3178             // [HGM] chat: intercept tells by users for which we have an open chat window
3179             channel = -1;
3180             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3181                                            looking_at(buf, &i, "* whispers:") ||
3182                                            looking_at(buf, &i, "* kibitzes:") ||
3183                                            looking_at(buf, &i, "* shouts:") ||
3184                                            looking_at(buf, &i, "* c-shouts:") ||
3185                                            looking_at(buf, &i, "--> * ") ||
3186                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3187                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3188                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3189                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3190                 int p;
3191                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3192                 chattingPartner = -1;
3193
3194                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3195                 for(p=0; p<MAX_CHAT; p++) {
3196                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3197                     talker[0] = '['; strcat(talker, "] ");
3198                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3199                     chattingPartner = p; break;
3200                     }
3201                 } else
3202                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3203                 for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("kibitzes", chatPartner[p])) {
3205                         talker[0] = '['; strcat(talker, "] ");
3206                         chattingPartner = p; break;
3207                     }
3208                 } else
3209                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3210                 for(p=0; p<MAX_CHAT; p++) {
3211                     if(!strcmp("whispers", chatPartner[p])) {
3212                         talker[0] = '['; strcat(talker, "] ");
3213                         chattingPartner = p; break;
3214                     }
3215                 } else
3216                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3217                   if(buf[i-8] == '-' && buf[i-3] == 't')
3218                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3219                     if(!strcmp("c-shouts", chatPartner[p])) {
3220                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3221                         chattingPartner = p; break;
3222                     }
3223                   }
3224                   if(chattingPartner < 0)
3225                   for(p=0; p<MAX_CHAT; p++) {
3226                     if(!strcmp("shouts", chatPartner[p])) {
3227                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3228                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3229                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3230                         chattingPartner = p; break;
3231                     }
3232                   }
3233                 }
3234                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3235                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3236                     talker[0] = 0; Colorize(ColorTell, FALSE);
3237                     chattingPartner = p; break;
3238                 }
3239                 if(chattingPartner<0) i = oldi; else {
3240                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3241                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3242                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                     started = STARTED_COMMENT;
3244                     parse_pos = 0; parse[0] = NULLCHAR;
3245                     savingComment = 3 + chattingPartner; // counts as TRUE
3246                     suppressKibitz = TRUE;
3247                     continue;
3248                 }
3249             } // [HGM] chat: end of patch
3250
3251           backup = i;
3252             if (appData.zippyTalk || appData.zippyPlay) {
3253                 /* [DM] Backup address for color zippy lines */
3254 #if ZIPPY
3255                if (loggedOn == TRUE)
3256                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3257                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3258 #endif
3259             } // [DM] 'else { ' deleted
3260                 if (
3261                     /* Regular tells and says */
3262                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3263                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3264                     looking_at(buf, &i, "* says: ") ||
3265                     /* Don't color "message" or "messages" output */
3266                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3267                     looking_at(buf, &i, "*. * at *:*: ") ||
3268                     looking_at(buf, &i, "--* (*:*): ") ||
3269                     /* Message notifications (same color as tells) */
3270                     looking_at(buf, &i, "* has left a message ") ||
3271                     looking_at(buf, &i, "* just sent you a message:\n") ||
3272                     /* Whispers and kibitzes */
3273                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3274                     looking_at(buf, &i, "* kibitzes: ") ||
3275                     /* Channel tells */
3276                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3277
3278                   if (tkind == 1 && strchr(star_match[0], ':')) {
3279                       /* Avoid "tells you:" spoofs in channels */
3280                      tkind = 3;
3281                   }
3282                   if (star_match[0][0] == NULLCHAR ||
3283                       strchr(star_match[0], ' ') ||
3284                       (tkind == 3 && strchr(star_match[1], ' '))) {
3285                     /* Reject bogus matches */
3286                     i = oldi;
3287                   } else {
3288                     if (appData.colorize) {
3289                       if (oldi > next_out) {
3290                         SendToPlayer(&buf[next_out], oldi - next_out);
3291                         next_out = oldi;
3292                       }
3293                       switch (tkind) {
3294                       case 1:
3295                         Colorize(ColorTell, FALSE);
3296                         curColor = ColorTell;
3297                         break;
3298                       case 2:
3299                         Colorize(ColorKibitz, FALSE);
3300                         curColor = ColorKibitz;
3301                         break;
3302                       case 3:
3303                         p = strrchr(star_match[1], '(');
3304                         if (p == NULL) {
3305                           p = star_match[1];
3306                         } else {
3307                           p++;
3308                         }
3309                         if (atoi(p) == 1) {
3310                           Colorize(ColorChannel1, FALSE);
3311                           curColor = ColorChannel1;
3312                         } else {
3313                           Colorize(ColorChannel, FALSE);
3314                           curColor = ColorChannel;
3315                         }
3316                         break;
3317                       case 5:
3318                         curColor = ColorNormal;
3319                         break;
3320                       }
3321                     }
3322                     if (started == STARTED_NONE && appData.autoComment &&
3323                         (gameMode == IcsObserving ||
3324                          gameMode == IcsPlayingWhite ||
3325                          gameMode == IcsPlayingBlack)) {
3326                       parse_pos = i - oldi;
3327                       memcpy(parse, &buf[oldi], parse_pos);
3328                       parse[parse_pos] = NULLCHAR;
3329                       started = STARTED_COMMENT;
3330                       savingComment = TRUE;
3331                     } else {
3332                       started = STARTED_CHATTER;
3333                       savingComment = FALSE;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                   }
3338                 }
3339
3340                 if (looking_at(buf, &i, "* s-shouts: ") ||
3341                     looking_at(buf, &i, "* c-shouts: ")) {
3342                     if (appData.colorize) {
3343                         if (oldi > next_out) {
3344                             SendToPlayer(&buf[next_out], oldi - next_out);
3345                             next_out = oldi;
3346                         }
3347                         Colorize(ColorSShout, FALSE);
3348                         curColor = ColorSShout;
3349                     }
3350                     loggedOn = TRUE;
3351                     started = STARTED_CHATTER;
3352                     continue;
3353                 }
3354
3355                 if (looking_at(buf, &i, "--->")) {
3356                     loggedOn = TRUE;
3357                     continue;
3358                 }
3359
3360                 if (looking_at(buf, &i, "* shouts: ") ||
3361                     looking_at(buf, &i, "--> ")) {
3362                     if (appData.colorize) {
3363                         if (oldi > next_out) {
3364                             SendToPlayer(&buf[next_out], oldi - next_out);
3365                             next_out = oldi;
3366                         }
3367                         Colorize(ColorShout, FALSE);
3368                         curColor = ColorShout;
3369                     }
3370                     loggedOn = TRUE;
3371                     started = STARTED_CHATTER;
3372                     continue;
3373                 }
3374
3375                 if (looking_at( buf, &i, "Challenge:")) {
3376                     if (appData.colorize) {
3377                         if (oldi > next_out) {
3378                             SendToPlayer(&buf[next_out], oldi - next_out);
3379                             next_out = oldi;
3380                         }
3381                         Colorize(ColorChallenge, FALSE);
3382                         curColor = ColorChallenge;
3383                     }
3384                     loggedOn = TRUE;
3385                     continue;
3386                 }
3387
3388                 if (looking_at(buf, &i, "* offers you") ||
3389                     looking_at(buf, &i, "* offers to be") ||
3390                     looking_at(buf, &i, "* would like to") ||
3391                     looking_at(buf, &i, "* requests to") ||
3392                     looking_at(buf, &i, "Your opponent offers") ||
3393                     looking_at(buf, &i, "Your opponent requests")) {
3394
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorRequest, FALSE);
3401                         curColor = ColorRequest;
3402                     }
3403                     continue;
3404                 }
3405
3406                 if (looking_at(buf, &i, "* (*) seeking")) {
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorSeek, FALSE);
3413                         curColor = ColorSeek;
3414                     }
3415                     continue;
3416             }
3417
3418           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3419
3420             if (looking_at(buf, &i, "\\   ")) {
3421                 if (prevColor != ColorNormal) {
3422                     if (oldi > next_out) {
3423                         SendToPlayer(&buf[next_out], oldi - next_out);
3424                         next_out = oldi;
3425                     }
3426                     Colorize(prevColor, TRUE);
3427                     curColor = prevColor;
3428                 }
3429                 if (savingComment) {
3430                     parse_pos = i - oldi;
3431                     memcpy(parse, &buf[oldi], parse_pos);
3432                     parse[parse_pos] = NULLCHAR;
3433                     started = STARTED_COMMENT;
3434                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3435                         chattingPartner = savingComment - 3; // kludge to remember the box
3436                 } else {
3437                     started = STARTED_CHATTER;
3438                 }
3439                 continue;
3440             }
3441
3442             if (looking_at(buf, &i, "Black Strength :") ||
3443                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3444                 looking_at(buf, &i, "<10>") ||
3445                 looking_at(buf, &i, "#@#")) {
3446                 /* Wrong board style */
3447                 loggedOn = TRUE;
3448                 SendToICS(ics_prefix);
3449                 SendToICS("set style 12\n");
3450                 SendToICS(ics_prefix);
3451                 SendToICS("refresh\n");
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i, "login:")) {
3456               if (!have_sent_ICS_logon) {
3457                 if(ICSInitScript())
3458                   have_sent_ICS_logon = 1;
3459                 else // no init script was found
3460                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3461               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3462                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3463               }
3464                 continue;
3465             }
3466
3467             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3468                 (looking_at(buf, &i, "\n<12> ") ||
3469                  looking_at(buf, &i, "<12> "))) {
3470                 loggedOn = TRUE;
3471                 if (oldi > next_out) {
3472                     SendToPlayer(&buf[next_out], oldi - next_out);
3473                 }
3474                 next_out = i;
3475                 started = STARTED_BOARD;
3476                 parse_pos = 0;
3477                 continue;
3478             }
3479
3480             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3481                 looking_at(buf, &i, "<b1> ")) {
3482                 if (oldi > next_out) {
3483                     SendToPlayer(&buf[next_out], oldi - next_out);
3484                 }
3485                 next_out = i;
3486                 started = STARTED_HOLDINGS;
3487                 parse_pos = 0;
3488                 continue;
3489             }
3490
3491             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3492                 loggedOn = TRUE;
3493                 /* Header for a move list -- first line */
3494
3495                 switch (ics_getting_history) {
3496                   case H_FALSE:
3497                     switch (gameMode) {
3498                       case IcsIdle:
3499                       case BeginningOfGame:
3500                         /* User typed "moves" or "oldmoves" while we
3501                            were idle.  Pretend we asked for these
3502                            moves and soak them up so user can step
3503                            through them and/or save them.
3504                            */
3505                         Reset(FALSE, TRUE);
3506                         gameMode = IcsObserving;
3507                         ModeHighlight();
3508                         ics_gamenum = -1;
3509                         ics_getting_history = H_GOT_UNREQ_HEADER;
3510                         break;
3511                       case EditGame: /*?*/
3512                       case EditPosition: /*?*/
3513                         /* Should above feature work in these modes too? */
3514                         /* For now it doesn't */
3515                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3516                         break;
3517                       default:
3518                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3519                         break;
3520                     }
3521                     break;
3522                   case H_REQUESTED:
3523                     /* Is this the right one? */
3524                     if (gameInfo.white && gameInfo.black &&
3525                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3526                         strcmp(gameInfo.black, star_match[2]) == 0) {
3527                         /* All is well */
3528                         ics_getting_history = H_GOT_REQ_HEADER;
3529                     }
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                   case H_GOT_UNREQ_HEADER:
3533                   case H_GOT_UNWANTED_HEADER:
3534                   case H_GETTING_MOVES:
3535                     /* Should not happen */
3536                     DisplayError(_("Error gathering move list: two headers"), 0);
3537                     ics_getting_history = H_FALSE;
3538                     break;
3539                 }
3540
3541                 /* Save player ratings into gameInfo if needed */
3542                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3543                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3544                     (gameInfo.whiteRating == -1 ||
3545                      gameInfo.blackRating == -1)) {
3546
3547                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3548                     gameInfo.blackRating = string_to_rating(star_match[3]);
3549                     if (appData.debugMode)
3550                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3551                               gameInfo.whiteRating, gameInfo.blackRating);
3552                 }
3553                 continue;
3554             }
3555
3556             if (looking_at(buf, &i,
3557               "* * match, initial time: * minute*, increment: * second")) {
3558                 /* Header for a move list -- second line */
3559                 /* Initial board will follow if this is a wild game */
3560                 if (gameInfo.event != NULL) free(gameInfo.event);
3561                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3562                 gameInfo.event = StrSave(str);
3563                 /* [HGM] we switched variant. Translate boards if needed. */
3564                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "Move  ")) {
3569                 /* Beginning of a move list */
3570                 switch (ics_getting_history) {
3571                   case H_FALSE:
3572                     /* Normally should not happen */
3573                     /* Maybe user hit reset while we were parsing */
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Happens if we are ignoring a move list that is not
3577                      * the one we just requested.  Common if the user
3578                      * tries to observe two games without turning off
3579                      * getMoveList */
3580                     break;
3581                   case H_GETTING_MOVES:
3582                     /* Should not happen */
3583                     DisplayError(_("Error gathering move list: nested"), 0);
3584                     ics_getting_history = H_FALSE;
3585                     break;
3586                   case H_GOT_REQ_HEADER:
3587                     ics_getting_history = H_GETTING_MOVES;
3588                     started = STARTED_MOVES;
3589                     parse_pos = 0;
3590                     if (oldi > next_out) {
3591                         SendToPlayer(&buf[next_out], oldi - next_out);
3592                     }
3593                     break;
3594                   case H_GOT_UNREQ_HEADER:
3595                     ics_getting_history = H_GETTING_MOVES;
3596                     started = STARTED_MOVES_NOHIDE;
3597                     parse_pos = 0;
3598                     break;
3599                   case H_GOT_UNWANTED_HEADER:
3600                     ics_getting_history = H_FALSE;
3601                     break;
3602                 }
3603                 continue;
3604             }
3605
3606             if (looking_at(buf, &i, "% ") ||
3607                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3608                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3609                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3610                     soughtPending = FALSE;
3611                     seekGraphUp = TRUE;
3612                     DrawSeekGraph();
3613                 }
3614                 if(suppressKibitz) next_out = i;
3615                 savingComment = FALSE;
3616                 suppressKibitz = 0;
3617                 switch (started) {
3618                   case STARTED_MOVES:
3619                   case STARTED_MOVES_NOHIDE:
3620                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3621                     parse[parse_pos + i - oldi] = NULLCHAR;
3622                     ParseGameHistory(parse);
3623 #if ZIPPY
3624                     if (appData.zippyPlay && first.initDone) {
3625                         FeedMovesToProgram(&first, forwardMostMove);
3626                         if (gameMode == IcsPlayingWhite) {
3627                             if (WhiteOnMove(forwardMostMove)) {
3628                                 if (first.sendTime) {
3629                                   if (first.useColors) {
3630                                     SendToProgram("black\n", &first);
3631                                   }
3632                                   SendTimeRemaining(&first, TRUE);
3633                                 }
3634                                 if (first.useColors) {
3635                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3636                                 }
3637                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3638                                 first.maybeThinking = TRUE;
3639                             } else {
3640                                 if (first.usePlayother) {
3641                                   if (first.sendTime) {
3642                                     SendTimeRemaining(&first, TRUE);
3643                                   }
3644                                   SendToProgram("playother\n", &first);
3645                                   firstMove = FALSE;
3646                                 } else {
3647                                   firstMove = TRUE;
3648                                 }
3649                             }
3650                         } else if (gameMode == IcsPlayingBlack) {
3651                             if (!WhiteOnMove(forwardMostMove)) {
3652                                 if (first.sendTime) {
3653                                   if (first.useColors) {
3654                                     SendToProgram("white\n", &first);
3655                                   }
3656                                   SendTimeRemaining(&first, FALSE);
3657                                 }
3658                                 if (first.useColors) {
3659                                   SendToProgram("black\n", &first);
3660                                 }
3661                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3662                                 first.maybeThinking = TRUE;
3663                             } else {
3664                                 if (first.usePlayother) {
3665                                   if (first.sendTime) {
3666                                     SendTimeRemaining(&first, FALSE);
3667                                   }
3668                                   SendToProgram("playother\n", &first);
3669                                   firstMove = FALSE;
3670                                 } else {
3671                                   firstMove = TRUE;
3672                                 }
3673                             }
3674                         }
3675                     }
3676 #endif
3677                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3678                         /* Moves came from oldmoves or moves command
3679                            while we weren't doing anything else.
3680                            */
3681                         currentMove = forwardMostMove;
3682                         ClearHighlights();/*!!could figure this out*/
3683                         flipView = appData.flipView;
3684                         DrawPosition(TRUE, boards[currentMove]);
3685                         DisplayBothClocks();
3686                         snprintf(str, MSG_SIZ, "%s %s %s",
3687                                 gameInfo.white, _("vs."),  gameInfo.black);
3688                         DisplayTitle(str);
3689                         gameMode = IcsIdle;
3690                     } else {
3691                         /* Moves were history of an active game */
3692                         if (gameInfo.resultDetails != NULL) {
3693                             free(gameInfo.resultDetails);
3694                             gameInfo.resultDetails = NULL;
3695                         }
3696                     }
3697                     HistorySet(parseList, backwardMostMove,
3698                                forwardMostMove, currentMove-1);
3699                     DisplayMove(currentMove - 1);
3700                     if (started == STARTED_MOVES) next_out = i;
3701                     started = STARTED_NONE;
3702                     ics_getting_history = H_FALSE;
3703                     break;
3704
3705                   case STARTED_OBSERVE:
3706                     started = STARTED_NONE;
3707                     SendToICS(ics_prefix);
3708                     SendToICS("refresh\n");
3709                     break;
3710
3711                   default:
3712                     break;
3713                 }
3714                 if(bookHit) { // [HGM] book: simulate book reply
3715                     static char bookMove[MSG_SIZ]; // a bit generous?
3716
3717                     programStats.nodes = programStats.depth = programStats.time =
3718                     programStats.score = programStats.got_only_move = 0;
3719                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3720
3721                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3722                     strcat(bookMove, bookHit);
3723                     HandleMachineMove(bookMove, &first);
3724                 }
3725                 continue;
3726             }
3727
3728             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3729                  started == STARTED_HOLDINGS ||
3730                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3731                 /* Accumulate characters in move list or board */
3732                 parse[parse_pos++] = buf[i];
3733             }
3734
3735             /* Start of game messages.  Mostly we detect start of game
3736                when the first board image arrives.  On some versions
3737                of the ICS, though, we need to do a "refresh" after starting
3738                to observe in order to get the current board right away. */
3739             if (looking_at(buf, &i, "Adding game * to observation list")) {
3740                 started = STARTED_OBSERVE;
3741                 continue;
3742             }
3743
3744             /* Handle auto-observe */
3745             if (appData.autoObserve &&
3746                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3747                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3748                 char *player;
3749                 /* Choose the player that was highlighted, if any. */
3750                 if (star_match[0][0] == '\033' ||
3751                     star_match[1][0] != '\033') {
3752                     player = star_match[0];
3753                 } else {
3754                     player = star_match[2];
3755                 }
3756                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3757                         ics_prefix, StripHighlightAndTitle(player));
3758                 SendToICS(str);
3759
3760                 /* Save ratings from notify string */
3761                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3762                 player1Rating = string_to_rating(star_match[1]);
3763                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3764                 player2Rating = string_to_rating(star_match[3]);
3765
3766                 if (appData.debugMode)
3767                   fprintf(debugFP,
3768                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3769                           player1Name, player1Rating,
3770                           player2Name, player2Rating);
3771
3772                 continue;
3773             }
3774
3775             /* Deal with automatic examine mode after a game,
3776                and with IcsObserving -> IcsExamining transition */
3777             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3778                 looking_at(buf, &i, "has made you an examiner of game *")) {
3779
3780                 int gamenum = atoi(star_match[0]);
3781                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3782                     gamenum == ics_gamenum) {
3783                     /* We were already playing or observing this game;
3784                        no need to refetch history */
3785                     gameMode = IcsExamining;
3786                     if (pausing) {
3787                         pauseExamForwardMostMove = forwardMostMove;
3788                     } else if (currentMove < forwardMostMove) {
3789                         ForwardInner(forwardMostMove);
3790                     }
3791                 } else {
3792                     /* I don't think this case really can happen */
3793                     SendToICS(ics_prefix);
3794                     SendToICS("refresh\n");
3795                 }
3796                 continue;
3797             }
3798
3799             /* Error messages */
3800 //          if (ics_user_moved) {
3801             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3802                 if (looking_at(buf, &i, "Illegal move") ||
3803                     looking_at(buf, &i, "Not a legal move") ||
3804                     looking_at(buf, &i, "Your king is in check") ||
3805                     looking_at(buf, &i, "It isn't your turn") ||
3806                     looking_at(buf, &i, "It is not your move")) {
3807                     /* Illegal move */
3808                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3809                         currentMove = forwardMostMove-1;
3810                         DisplayMove(currentMove - 1); /* before DMError */
3811                         DrawPosition(FALSE, boards[currentMove]);
3812                         SwitchClocks(forwardMostMove-1); // [HGM] race
3813                         DisplayBothClocks();
3814                     }
3815                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3816                     ics_user_moved = 0;
3817                     continue;
3818                 }
3819             }
3820
3821             if (looking_at(buf, &i, "still have time") ||
3822                 looking_at(buf, &i, "not out of time") ||
3823                 looking_at(buf, &i, "either player is out of time") ||
3824                 looking_at(buf, &i, "has timeseal; checking")) {
3825                 /* We must have called his flag a little too soon */
3826                 whiteFlag = blackFlag = FALSE;
3827                 continue;
3828             }
3829
3830             if (looking_at(buf, &i, "added * seconds to") ||
3831                 looking_at(buf, &i, "seconds were added to")) {
3832                 /* Update the clocks */
3833                 SendToICS(ics_prefix);
3834                 SendToICS("refresh\n");
3835                 continue;
3836             }
3837
3838             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3839                 ics_clock_paused = TRUE;
3840                 StopClocks();
3841                 continue;
3842             }
3843
3844             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3845                 ics_clock_paused = FALSE;
3846                 StartClocks();
3847                 continue;
3848             }
3849
3850             /* Grab player ratings from the Creating: message.
3851                Note we have to check for the special case when
3852                the ICS inserts things like [white] or [black]. */
3853             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3854                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3855                 /* star_matches:
3856                    0    player 1 name (not necessarily white)
3857                    1    player 1 rating
3858                    2    empty, white, or black (IGNORED)
3859                    3    player 2 name (not necessarily black)
3860                    4    player 2 rating
3861
3862                    The names/ratings are sorted out when the game
3863                    actually starts (below).
3864                 */
3865                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3866                 player1Rating = string_to_rating(star_match[1]);
3867                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3868                 player2Rating = string_to_rating(star_match[4]);
3869
3870                 if (appData.debugMode)
3871                   fprintf(debugFP,
3872                           "Ratings from 'Creating:' %s %d, %s %d\n",
3873                           player1Name, player1Rating,
3874                           player2Name, player2Rating);
3875
3876                 continue;
3877             }
3878
3879             /* Improved generic start/end-of-game messages */
3880             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3881                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3882                 /* If tkind == 0: */
3883                 /* star_match[0] is the game number */
3884                 /*           [1] is the white player's name */
3885                 /*           [2] is the black player's name */
3886                 /* For end-of-game: */
3887                 /*           [3] is the reason for the game end */
3888                 /*           [4] is a PGN end game-token, preceded by " " */
3889                 /* For start-of-game: */
3890                 /*           [3] begins with "Creating" or "Continuing" */
3891                 /*           [4] is " *" or empty (don't care). */
3892                 int gamenum = atoi(star_match[0]);
3893                 char *whitename, *blackname, *why, *endtoken;
3894                 ChessMove endtype = EndOfFile;
3895
3896                 if (tkind == 0) {
3897                   whitename = star_match[1];
3898                   blackname = star_match[2];
3899                   why = star_match[3];
3900                   endtoken = star_match[4];
3901                 } else {
3902                   whitename = star_match[1];
3903                   blackname = star_match[3];
3904                   why = star_match[5];
3905                   endtoken = star_match[6];
3906                 }
3907
3908                 /* Game start messages */
3909                 if (strncmp(why, "Creating ", 9) == 0 ||
3910                     strncmp(why, "Continuing ", 11) == 0) {
3911                     gs_gamenum = gamenum;
3912                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3913                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3914                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3915 #if ZIPPY
3916                     if (appData.zippyPlay) {
3917                         ZippyGameStart(whitename, blackname);
3918                     }
3919 #endif /*ZIPPY*/
3920                     partnerBoardValid = FALSE; // [HGM] bughouse
3921                     continue;
3922                 }
3923
3924                 /* Game end messages */
3925                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3926                     ics_gamenum != gamenum) {
3927                     continue;
3928                 }
3929                 while (endtoken[0] == ' ') endtoken++;
3930                 switch (endtoken[0]) {
3931                   case '*':
3932                   default:
3933                     endtype = GameUnfinished;
3934                     break;
3935                   case '0':
3936                     endtype = BlackWins;
3937                     break;
3938                   case '1':
3939                     if (endtoken[1] == '/')
3940                       endtype = GameIsDrawn;
3941                     else
3942                       endtype = WhiteWins;
3943                     break;
3944                 }
3945                 GameEnds(endtype, why, GE_ICS);
3946 #if ZIPPY
3947                 if (appData.zippyPlay && first.initDone) {
3948                     ZippyGameEnd(endtype, why);
3949                     if (first.pr == NoProc) {
3950                       /* Start the next process early so that we'll
3951                          be ready for the next challenge */
3952                       StartChessProgram(&first);
3953                     }
3954                     /* Send "new" early, in case this command takes
3955                        a long time to finish, so that we'll be ready
3956                        for the next challenge. */
3957                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3958                     Reset(TRUE, TRUE);
3959                 }
3960 #endif /*ZIPPY*/
3961                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "Removing game * from observation") ||
3966                 looking_at(buf, &i, "no longer observing game *") ||
3967                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3968                 if (gameMode == IcsObserving &&
3969                     atoi(star_match[0]) == ics_gamenum)
3970                   {
3971                       /* icsEngineAnalyze */
3972                       if (appData.icsEngineAnalyze) {
3973                             ExitAnalyzeMode();
3974                             ModeHighlight();
3975                       }
3976                       StopClocks();
3977                       gameMode = IcsIdle;
3978                       ics_gamenum = -1;
3979                       ics_user_moved = FALSE;
3980                   }
3981                 continue;
3982             }
3983
3984             if (looking_at(buf, &i, "no longer examining game *")) {
3985                 if (gameMode == IcsExamining &&
3986                     atoi(star_match[0]) == ics_gamenum)
3987                   {
3988                       gameMode = IcsIdle;
3989                       ics_gamenum = -1;
3990                       ics_user_moved = FALSE;
3991                   }
3992                 continue;
3993             }
3994
3995             /* Advance leftover_start past any newlines we find,
3996                so only partial lines can get reparsed */
3997             if (looking_at(buf, &i, "\n")) {
3998                 prevColor = curColor;
3999                 if (curColor != ColorNormal) {
4000                     if (oldi > next_out) {
4001                         SendToPlayer(&buf[next_out], oldi - next_out);
4002                         next_out = oldi;
4003                     }
4004                     Colorize(ColorNormal, FALSE);
4005                     curColor = ColorNormal;
4006                 }
4007                 if (started == STARTED_BOARD) {
4008                     started = STARTED_NONE;
4009                     parse[parse_pos] = NULLCHAR;
4010                     ParseBoard12(parse);
4011                     ics_user_moved = 0;
4012
4013                     /* Send premove here */
4014                     if (appData.premove) {
4015                       char str[MSG_SIZ];
4016                       if (currentMove == 0 &&
4017                           gameMode == IcsPlayingWhite &&
4018                           appData.premoveWhite) {
4019                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4020                         if (appData.debugMode)
4021                           fprintf(debugFP, "Sending premove:\n");
4022                         SendToICS(str);
4023                       } else if (currentMove == 1 &&
4024                                  gameMode == IcsPlayingBlack &&
4025                                  appData.premoveBlack) {
4026                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4027                         if (appData.debugMode)
4028                           fprintf(debugFP, "Sending premove:\n");
4029                         SendToICS(str);
4030                       } else if (gotPremove) {
4031                         gotPremove = 0;
4032                         ClearPremoveHighlights();
4033                         if (appData.debugMode)
4034                           fprintf(debugFP, "Sending premove:\n");
4035                           UserMoveEvent(premoveFromX, premoveFromY,
4036                                         premoveToX, premoveToY,
4037                                         premovePromoChar);
4038                       }
4039                     }
4040
4041                     /* Usually suppress following prompt */
4042                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4043                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4044                         if (looking_at(buf, &i, "*% ")) {
4045                             savingComment = FALSE;
4046                             suppressKibitz = 0;
4047                         }
4048                     }
4049                     next_out = i;
4050                 } else if (started == STARTED_HOLDINGS) {
4051                     int gamenum;
4052                     char new_piece[MSG_SIZ];
4053                     started = STARTED_NONE;
4054                     parse[parse_pos] = NULLCHAR;
4055                     if (appData.debugMode)
4056                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4057                                                         parse, currentMove);
4058                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4059                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4060                         if (gameInfo.variant == VariantNormal) {
4061                           /* [HGM] We seem to switch variant during a game!
4062                            * Presumably no holdings were displayed, so we have
4063                            * to move the position two files to the right to
4064                            * create room for them!
4065                            */
4066                           VariantClass newVariant;
4067                           switch(gameInfo.boardWidth) { // base guess on board width
4068                                 case 9:  newVariant = VariantShogi; break;
4069                                 case 10: newVariant = VariantGreat; break;
4070                                 default: newVariant = VariantCrazyhouse; break;
4071                           }
4072                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4073                           /* Get a move list just to see the header, which
4074                              will tell us whether this is really bug or zh */
4075                           if (ics_getting_history == H_FALSE) {
4076                             ics_getting_history = H_REQUESTED;
4077                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4078                             SendToICS(str);
4079                           }
4080                         }
4081                         new_piece[0] = NULLCHAR;
4082                         sscanf(parse, "game %d white [%s black [%s <- %s",
4083                                &gamenum, white_holding, black_holding,
4084                                new_piece);
4085                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4086                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4087                         /* [HGM] copy holdings to board holdings area */
4088                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4089                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4090                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4091 #if ZIPPY
4092                         if (appData.zippyPlay && first.initDone) {
4093                             ZippyHoldings(white_holding, black_holding,
4094                                           new_piece);
4095                         }
4096 #endif /*ZIPPY*/
4097                         if (tinyLayout || smallLayout) {
4098                             char wh[16], bh[16];
4099                             PackHolding(wh, white_holding);
4100                             PackHolding(bh, black_holding);
4101                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4102                                     gameInfo.white, gameInfo.black);
4103                         } else {
4104                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4105                                     gameInfo.white, white_holding, _("vs."),
4106                                     gameInfo.black, black_holding);
4107                         }
4108                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4109                         DrawPosition(FALSE, boards[currentMove]);
4110                         DisplayTitle(str);
4111                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4112                         sscanf(parse, "game %d white [%s black [%s <- %s",
4113                                &gamenum, white_holding, black_holding,
4114                                new_piece);
4115                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4116                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4117                         /* [HGM] copy holdings to partner-board holdings area */
4118                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4119                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4120                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4121                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4122                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4123                       }
4124                     }
4125                     /* Suppress following prompt */
4126                     if (looking_at(buf, &i, "*% ")) {
4127                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4128                         savingComment = FALSE;
4129                         suppressKibitz = 0;
4130                     }
4131                     next_out = i;
4132                 }
4133                 continue;
4134             }
4135
4136             i++;                /* skip unparsed character and loop back */
4137         }
4138
4139         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4140 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4141 //          SendToPlayer(&buf[next_out], i - next_out);
4142             started != STARTED_HOLDINGS && leftover_start > next_out) {
4143             SendToPlayer(&buf[next_out], leftover_start - next_out);
4144             next_out = i;
4145         }
4146
4147         leftover_len = buf_len - leftover_start;
4148         /* if buffer ends with something we couldn't parse,
4149            reparse it after appending the next read */
4150
4151     } else if (count == 0) {
4152         RemoveInputSource(isr);
4153         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4154     } else {
4155         DisplayFatalError(_("Error reading from ICS"), error, 1);
4156     }
4157 }
4158
4159
4160 /* Board style 12 looks like this:
4161
4162    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4163
4164  * The "<12> " is stripped before it gets to this routine.  The two
4165  * trailing 0's (flip state and clock ticking) are later addition, and
4166  * some chess servers may not have them, or may have only the first.
4167  * Additional trailing fields may be added in the future.
4168  */
4169
4170 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4171
4172 #define RELATION_OBSERVING_PLAYED    0
4173 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4174 #define RELATION_PLAYING_MYMOVE      1
4175 #define RELATION_PLAYING_NOTMYMOVE  -1
4176 #define RELATION_EXAMINING           2
4177 #define RELATION_ISOLATED_BOARD     -3
4178 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4179
4180 void
4181 ParseBoard12 (char *string)
4182 {
4183     GameMode newGameMode;
4184     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4185     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4186     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4187     char to_play, board_chars[200];
4188     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4189     char black[32], white[32];
4190     Board board;
4191     int prevMove = currentMove;
4192     int ticking = 2;
4193     ChessMove moveType;
4194     int fromX, fromY, toX, toY;
4195     char promoChar;
4196     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4197     char *bookHit = NULL; // [HGM] book
4198     Boolean weird = FALSE, reqFlag = FALSE;
4199
4200     fromX = fromY = toX = toY = -1;
4201
4202     newGame = FALSE;
4203
4204     if (appData.debugMode)
4205       fprintf(debugFP, _("Parsing board: %s\n"), string);
4206
4207     move_str[0] = NULLCHAR;
4208     elapsed_time[0] = NULLCHAR;
4209     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4210         int  i = 0, j;
4211         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4212             if(string[i] == ' ') { ranks++; files = 0; }
4213             else files++;
4214             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4215             i++;
4216         }
4217         for(j = 0; j <i; j++) board_chars[j] = string[j];
4218         board_chars[i] = '\0';
4219         string += i + 1;
4220     }
4221     n = sscanf(string, PATTERN, &to_play, &double_push,
4222                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4223                &gamenum, white, black, &relation, &basetime, &increment,
4224                &white_stren, &black_stren, &white_time, &black_time,
4225                &moveNum, str, elapsed_time, move_str, &ics_flip,
4226                &ticking);
4227
4228     if (n < 21) {
4229         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4230         DisplayError(str, 0);
4231         return;
4232     }
4233
4234     /* Convert the move number to internal form */
4235     moveNum = (moveNum - 1) * 2;
4236     if (to_play == 'B') moveNum++;
4237     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4238       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4239                         0, 1);
4240       return;
4241     }
4242
4243     switch (relation) {
4244       case RELATION_OBSERVING_PLAYED:
4245       case RELATION_OBSERVING_STATIC:
4246         if (gamenum == -1) {
4247             /* Old ICC buglet */
4248             relation = RELATION_OBSERVING_STATIC;
4249         }
4250         newGameMode = IcsObserving;
4251         break;
4252       case RELATION_PLAYING_MYMOVE:
4253       case RELATION_PLAYING_NOTMYMOVE:
4254         newGameMode =
4255           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4256             IcsPlayingWhite : IcsPlayingBlack;
4257         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4258         break;
4259       case RELATION_EXAMINING:
4260         newGameMode = IcsExamining;
4261         break;
4262       case RELATION_ISOLATED_BOARD:
4263       default:
4264         /* Just display this board.  If user was doing something else,
4265            we will forget about it until the next board comes. */
4266         newGameMode = IcsIdle;
4267         break;
4268       case RELATION_STARTING_POSITION:
4269         newGameMode = gameMode;
4270         break;
4271     }
4272
4273     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4274         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4275          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4276       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4277       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4278       static int lastBgGame = -1;
4279       char *toSqr;
4280       for (k = 0; k < ranks; k++) {
4281         for (j = 0; j < files; j++)
4282           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4283         if(gameInfo.holdingsWidth > 1) {
4284              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4285              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4286         }
4287       }
4288       CopyBoard(partnerBoard, board);
4289       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4290         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4291         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4292       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4293       if(toSqr = strchr(str, '-')) {
4294         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4295         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4296       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4297       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4298       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4299       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4300       if(twoBoards) {
4301           DisplayWhiteClock(white_time*fac, to_play == 'W');
4302           DisplayBlackClock(black_time*fac, to_play != 'W');
4303           activePartner = to_play;
4304           if(gamenum != lastBgGame) {
4305               char buf[MSG_SIZ];
4306               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4307               DisplayTitle(buf);
4308           }
4309           lastBgGame = gamenum;
4310           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4311                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4312       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4313                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4314       DisplayMessage(partnerStatus, "");
4315         partnerBoardValid = TRUE;
4316       return;
4317     }
4318
4319     if(appData.dualBoard && appData.bgObserve) {
4320         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4321             SendToICS(ics_prefix), SendToICS("pobserve\n");
4322         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4323             char buf[MSG_SIZ];
4324             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4325             SendToICS(buf);
4326         }
4327     }
4328
4329     /* Modify behavior for initial board display on move listing
4330        of wild games.
4331        */
4332     switch (ics_getting_history) {
4333       case H_FALSE:
4334       case H_REQUESTED:
4335         break;
4336       case H_GOT_REQ_HEADER:
4337       case H_GOT_UNREQ_HEADER:
4338         /* This is the initial position of the current game */
4339         gamenum = ics_gamenum;
4340         moveNum = 0;            /* old ICS bug workaround */
4341         if (to_play == 'B') {
4342           startedFromSetupPosition = TRUE;
4343           blackPlaysFirst = TRUE;
4344           moveNum = 1;
4345           if (forwardMostMove == 0) forwardMostMove = 1;
4346           if (backwardMostMove == 0) backwardMostMove = 1;
4347           if (currentMove == 0) currentMove = 1;
4348         }
4349         newGameMode = gameMode;
4350         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4351         break;
4352       case H_GOT_UNWANTED_HEADER:
4353         /* This is an initial board that we don't want */
4354         return;
4355       case H_GETTING_MOVES:
4356         /* Should not happen */
4357         DisplayError(_("Error gathering move list: extra board"), 0);
4358         ics_getting_history = H_FALSE;
4359         return;
4360     }
4361
4362    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4363                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4364                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4365      /* [HGM] We seem to have switched variant unexpectedly
4366       * Try to guess new variant from board size
4367       */
4368           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4369           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4370           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4371           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4372           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4373           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4374           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4375           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4376           /* Get a move list just to see the header, which
4377              will tell us whether this is really bug or zh */
4378           if (ics_getting_history == H_FALSE) {
4379             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4380             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4381             SendToICS(str);
4382           }
4383     }
4384
4385     /* Take action if this is the first board of a new game, or of a
4386        different game than is currently being displayed.  */
4387     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4388         relation == RELATION_ISOLATED_BOARD) {
4389
4390         /* Forget the old game and get the history (if any) of the new one */
4391         if (gameMode != BeginningOfGame) {
4392           Reset(TRUE, TRUE);
4393         }
4394         newGame = TRUE;
4395         if (appData.autoRaiseBoard) BoardToTop();
4396         prevMove = -3;
4397         if (gamenum == -1) {
4398             newGameMode = IcsIdle;
4399         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4400                    appData.getMoveList && !reqFlag) {
4401             /* Need to get game history */
4402             ics_getting_history = H_REQUESTED;
4403             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4404             SendToICS(str);
4405         }
4406
4407         /* Initially flip the board to have black on the bottom if playing
4408            black or if the ICS flip flag is set, but let the user change
4409            it with the Flip View button. */
4410         flipView = appData.autoFlipView ?
4411           (newGameMode == IcsPlayingBlack) || ics_flip :
4412           appData.flipView;
4413
4414         /* Done with values from previous mode; copy in new ones */
4415         gameMode = newGameMode;
4416         ModeHighlight();
4417         ics_gamenum = gamenum;
4418         if (gamenum == gs_gamenum) {
4419             int klen = strlen(gs_kind);
4420             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4421             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4422             gameInfo.event = StrSave(str);
4423         } else {
4424             gameInfo.event = StrSave("ICS game");
4425         }
4426         gameInfo.site = StrSave(appData.icsHost);
4427         gameInfo.date = PGNDate();
4428         gameInfo.round = StrSave("-");
4429         gameInfo.white = StrSave(white);
4430         gameInfo.black = StrSave(black);
4431         timeControl = basetime * 60 * 1000;
4432         timeControl_2 = 0;
4433         timeIncrement = increment * 1000;
4434         movesPerSession = 0;
4435         gameInfo.timeControl = TimeControlTagValue();
4436         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4437   if (appData.debugMode) {
4438     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4439     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4440     setbuf(debugFP, NULL);
4441   }
4442
4443         gameInfo.outOfBook = NULL;
4444
4445         /* Do we have the ratings? */
4446         if (strcmp(player1Name, white) == 0 &&
4447             strcmp(player2Name, black) == 0) {
4448             if (appData.debugMode)
4449               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4450                       player1Rating, player2Rating);
4451             gameInfo.whiteRating = player1Rating;
4452             gameInfo.blackRating = player2Rating;
4453         } else if (strcmp(player2Name, white) == 0 &&
4454                    strcmp(player1Name, black) == 0) {
4455             if (appData.debugMode)
4456               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4457                       player2Rating, player1Rating);
4458             gameInfo.whiteRating = player2Rating;
4459             gameInfo.blackRating = player1Rating;
4460         }
4461         player1Name[0] = player2Name[0] = NULLCHAR;
4462
4463         /* Silence shouts if requested */
4464         if (appData.quietPlay &&
4465             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4466             SendToICS(ics_prefix);
4467             SendToICS("set shout 0\n");
4468         }
4469     }
4470
4471     /* Deal with midgame name changes */
4472     if (!newGame) {
4473         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4474             if (gameInfo.white) free(gameInfo.white);
4475             gameInfo.white = StrSave(white);
4476         }
4477         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4478             if (gameInfo.black) free(gameInfo.black);
4479             gameInfo.black = StrSave(black);
4480         }
4481     }
4482
4483     /* Throw away game result if anything actually changes in examine mode */
4484     if (gameMode == IcsExamining && !newGame) {
4485         gameInfo.result = GameUnfinished;
4486         if (gameInfo.resultDetails != NULL) {
4487             free(gameInfo.resultDetails);
4488             gameInfo.resultDetails = NULL;
4489         }
4490     }
4491
4492     /* In pausing && IcsExamining mode, we ignore boards coming
4493        in if they are in a different variation than we are. */
4494     if (pauseExamInvalid) return;
4495     if (pausing && gameMode == IcsExamining) {
4496         if (moveNum <= pauseExamForwardMostMove) {
4497             pauseExamInvalid = TRUE;
4498             forwardMostMove = pauseExamForwardMostMove;
4499             return;
4500         }
4501     }
4502
4503   if (appData.debugMode) {
4504     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4505   }
4506     /* Parse the board */
4507     for (k = 0; k < ranks; k++) {
4508       for (j = 0; j < files; j++)
4509         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4510       if(gameInfo.holdingsWidth > 1) {
4511            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4512            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4513       }
4514     }
4515     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4516       board[5][BOARD_RGHT+1] = WhiteAngel;
4517       board[6][BOARD_RGHT+1] = WhiteMarshall;
4518       board[1][0] = BlackMarshall;
4519       board[2][0] = BlackAngel;
4520       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4521     }
4522     CopyBoard(boards[moveNum], board);
4523     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4524     if (moveNum == 0) {
4525         startedFromSetupPosition =
4526           !CompareBoards(board, initialPosition);
4527         if(startedFromSetupPosition)
4528             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4529     }
4530
4531     /* [HGM] Set castling rights. Take the outermost Rooks,
4532        to make it also work for FRC opening positions. Note that board12
4533        is really defective for later FRC positions, as it has no way to
4534        indicate which Rook can castle if they are on the same side of King.
4535        For the initial position we grant rights to the outermost Rooks,
4536        and remember thos rights, and we then copy them on positions
4537        later in an FRC game. This means WB might not recognize castlings with
4538        Rooks that have moved back to their original position as illegal,
4539        but in ICS mode that is not its job anyway.
4540     */
4541     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4542     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4543
4544         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4545             if(board[0][i] == WhiteRook) j = i;
4546         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4547         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4548             if(board[0][i] == WhiteRook) j = i;
4549         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4550         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4551             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4552         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4553         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4554             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4555         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4556
4557         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4558         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4559         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4560             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4561         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4562             if(board[BOARD_HEIGHT-1][k] == bKing)
4563                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4564         if(gameInfo.variant == VariantTwoKings) {
4565             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4566             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4567             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4568         }
4569     } else { int r;
4570         r = boards[moveNum][CASTLING][0] = initialRights[0];
4571         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4572         r = boards[moveNum][CASTLING][1] = initialRights[1];
4573         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4574         r = boards[moveNum][CASTLING][3] = initialRights[3];
4575         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4576         r = boards[moveNum][CASTLING][4] = initialRights[4];
4577         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4578         /* wildcastle kludge: always assume King has rights */
4579         r = boards[moveNum][CASTLING][2] = initialRights[2];
4580         r = boards[moveNum][CASTLING][5] = initialRights[5];
4581     }
4582     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4583     boards[moveNum][EP_STATUS] = EP_NONE;
4584     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4585     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4586     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4587
4588
4589     if (ics_getting_history == H_GOT_REQ_HEADER ||
4590         ics_getting_history == H_GOT_UNREQ_HEADER) {
4591         /* This was an initial position from a move list, not
4592            the current position */
4593         return;
4594     }
4595
4596     /* Update currentMove and known move number limits */
4597     newMove = newGame || moveNum > forwardMostMove;
4598
4599     if (newGame) {
4600         forwardMostMove = backwardMostMove = currentMove = moveNum;
4601         if (gameMode == IcsExamining && moveNum == 0) {
4602           /* Workaround for ICS limitation: we are not told the wild
4603              type when starting to examine a game.  But if we ask for
4604              the move list, the move list header will tell us */
4605             ics_getting_history = H_REQUESTED;
4606             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4607             SendToICS(str);
4608         }
4609     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4610                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4611 #if ZIPPY
4612         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4613         /* [HGM] applied this also to an engine that is silently watching        */
4614         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4615             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4616             gameInfo.variant == currentlyInitializedVariant) {
4617           takeback = forwardMostMove - moveNum;
4618           for (i = 0; i < takeback; i++) {
4619             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4620             SendToProgram("undo\n", &first);
4621           }
4622         }
4623 #endif
4624
4625         forwardMostMove = moveNum;
4626         if (!pausing || currentMove > forwardMostMove)
4627           currentMove = forwardMostMove;
4628     } else {
4629         /* New part of history that is not contiguous with old part */
4630         if (pausing && gameMode == IcsExamining) {
4631             pauseExamInvalid = TRUE;
4632             forwardMostMove = pauseExamForwardMostMove;
4633             return;
4634         }
4635         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4636 #if ZIPPY
4637             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4638                 // [HGM] when we will receive the move list we now request, it will be
4639                 // fed to the engine from the first move on. So if the engine is not
4640                 // in the initial position now, bring it there.
4641                 InitChessProgram(&first, 0);
4642             }
4643 #endif
4644             ics_getting_history = H_REQUESTED;
4645             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4646             SendToICS(str);
4647         }
4648         forwardMostMove = backwardMostMove = currentMove = moveNum;
4649     }
4650
4651     /* Update the clocks */
4652     if (strchr(elapsed_time, '.')) {
4653       /* Time is in ms */
4654       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4655       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4656     } else {
4657       /* Time is in seconds */
4658       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4659       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4660     }
4661
4662
4663 #if ZIPPY
4664     if (appData.zippyPlay && newGame &&
4665         gameMode != IcsObserving && gameMode != IcsIdle &&
4666         gameMode != IcsExamining)
4667       ZippyFirstBoard(moveNum, basetime, increment);
4668 #endif
4669
4670     /* Put the move on the move list, first converting
4671        to canonical algebraic form. */
4672     if (moveNum > 0) {
4673   if (appData.debugMode) {
4674     if (appData.debugMode) { int f = forwardMostMove;
4675         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4676                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4677                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4678     }
4679     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4680     fprintf(debugFP, "moveNum = %d\n", moveNum);
4681     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4682     setbuf(debugFP, NULL);
4683   }
4684         if (moveNum <= backwardMostMove) {
4685             /* We don't know what the board looked like before
4686                this move.  Punt. */
4687           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4688             strcat(parseList[moveNum - 1], " ");
4689             strcat(parseList[moveNum - 1], elapsed_time);
4690             moveList[moveNum - 1][0] = NULLCHAR;
4691         } else if (strcmp(move_str, "none") == 0) {
4692             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4693             /* Again, we don't know what the board looked like;
4694                this is really the start of the game. */
4695             parseList[moveNum - 1][0] = NULLCHAR;
4696             moveList[moveNum - 1][0] = NULLCHAR;
4697             backwardMostMove = moveNum;
4698             startedFromSetupPosition = TRUE;
4699             fromX = fromY = toX = toY = -1;
4700         } else {
4701           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4702           //                 So we parse the long-algebraic move string in stead of the SAN move
4703           int valid; char buf[MSG_SIZ], *prom;
4704
4705           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4706                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4707           // str looks something like "Q/a1-a2"; kill the slash
4708           if(str[1] == '/')
4709             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4710           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4711           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4712                 strcat(buf, prom); // long move lacks promo specification!
4713           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4714                 if(appData.debugMode)
4715                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4716                 safeStrCpy(move_str, buf, MSG_SIZ);
4717           }
4718           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4719                                 &fromX, &fromY, &toX, &toY, &promoChar)
4720                || ParseOneMove(buf, moveNum - 1, &moveType,
4721                                 &fromX, &fromY, &toX, &toY, &promoChar);
4722           // end of long SAN patch
4723           if (valid) {
4724             (void) CoordsToAlgebraic(boards[moveNum - 1],
4725                                      PosFlags(moveNum - 1),
4726                                      fromY, fromX, toY, toX, promoChar,
4727                                      parseList[moveNum-1]);
4728             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4729               case MT_NONE:
4730               case MT_STALEMATE:
4731               default:
4732                 break;
4733               case MT_CHECK:
4734                 if(gameInfo.variant != VariantShogi)
4735                     strcat(parseList[moveNum - 1], "+");
4736                 break;
4737               case MT_CHECKMATE:
4738               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4739                 strcat(parseList[moveNum - 1], "#");
4740                 break;
4741             }
4742             strcat(parseList[moveNum - 1], " ");
4743             strcat(parseList[moveNum - 1], elapsed_time);
4744             /* currentMoveString is set as a side-effect of ParseOneMove */
4745             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4746             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4747             strcat(moveList[moveNum - 1], "\n");
4748
4749             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4750                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4751               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4752                 ChessSquare old, new = boards[moveNum][k][j];
4753                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4754                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4755                   if(old == new) continue;
4756                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4757                   else if(new == WhiteWazir || new == BlackWazir) {
4758                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4759                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4760                       else boards[moveNum][k][j] = old; // preserve type of Gold
4761                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4762                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4763               }
4764           } else {
4765             /* Move from ICS was illegal!?  Punt. */
4766             if (appData.debugMode) {
4767               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4768               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4769             }
4770             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4771             strcat(parseList[moveNum - 1], " ");
4772             strcat(parseList[moveNum - 1], elapsed_time);
4773             moveList[moveNum - 1][0] = NULLCHAR;
4774             fromX = fromY = toX = toY = -1;
4775           }
4776         }
4777   if (appData.debugMode) {
4778     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4779     setbuf(debugFP, NULL);
4780   }
4781
4782 #if ZIPPY
4783         /* Send move to chess program (BEFORE animating it). */
4784         if (appData.zippyPlay && !newGame && newMove &&
4785            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4786
4787             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4788                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4789                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4790                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4791                             move_str);
4792                     DisplayError(str, 0);
4793                 } else {
4794                     if (first.sendTime) {
4795                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4796                     }
4797                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4798                     if (firstMove && !bookHit) {
4799                         firstMove = FALSE;
4800                         if (first.useColors) {
4801                           SendToProgram(gameMode == IcsPlayingWhite ?
4802                                         "white\ngo\n" :
4803                                         "black\ngo\n", &first);
4804                         } else {
4805                           SendToProgram("go\n", &first);
4806                         }
4807                         first.maybeThinking = TRUE;
4808                     }
4809                 }
4810             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4811               if (moveList[moveNum - 1][0] == NULLCHAR) {
4812                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4813                 DisplayError(str, 0);
4814               } else {
4815                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4816                 SendMoveToProgram(moveNum - 1, &first);
4817               }
4818             }
4819         }
4820 #endif
4821     }
4822
4823     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4824         /* If move comes from a remote source, animate it.  If it
4825            isn't remote, it will have already been animated. */
4826         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4827             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4828         }
4829         if (!pausing && appData.highlightLastMove) {
4830             SetHighlights(fromX, fromY, toX, toY);
4831         }
4832     }
4833
4834     /* Start the clocks */
4835     whiteFlag = blackFlag = FALSE;
4836     appData.clockMode = !(basetime == 0 && increment == 0);
4837     if (ticking == 0) {
4838       ics_clock_paused = TRUE;
4839       StopClocks();
4840     } else if (ticking == 1) {
4841       ics_clock_paused = FALSE;
4842     }
4843     if (gameMode == IcsIdle ||
4844         relation == RELATION_OBSERVING_STATIC ||
4845         relation == RELATION_EXAMINING ||
4846         ics_clock_paused)
4847       DisplayBothClocks();
4848     else
4849       StartClocks();
4850
4851     /* Display opponents and material strengths */
4852     if (gameInfo.variant != VariantBughouse &&
4853         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4854         if (tinyLayout || smallLayout) {
4855             if(gameInfo.variant == VariantNormal)
4856               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4857                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4858                     basetime, increment);
4859             else
4860               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4861                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4862                     basetime, increment, (int) gameInfo.variant);
4863         } else {
4864             if(gameInfo.variant == VariantNormal)
4865               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4866                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4867                     basetime, increment);
4868             else
4869               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4870                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4871                     basetime, increment, VariantName(gameInfo.variant));
4872         }
4873         DisplayTitle(str);
4874   if (appData.debugMode) {
4875     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4876   }
4877     }
4878
4879
4880     /* Display the board */
4881     if (!pausing && !appData.noGUI) {
4882
4883       if (appData.premove)
4884           if (!gotPremove ||
4885              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4886              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4887               ClearPremoveHighlights();
4888
4889       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4890         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4891       DrawPosition(j, boards[currentMove]);
4892
4893       DisplayMove(moveNum - 1);
4894       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4895             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4896               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4897         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4898       }
4899     }
4900
4901     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4902 #if ZIPPY
4903     if(bookHit) { // [HGM] book: simulate book reply
4904         static char bookMove[MSG_SIZ]; // a bit generous?
4905
4906         programStats.nodes = programStats.depth = programStats.time =
4907         programStats.score = programStats.got_only_move = 0;
4908         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4909
4910         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4911         strcat(bookMove, bookHit);
4912         HandleMachineMove(bookMove, &first);
4913     }
4914 #endif
4915 }
4916
4917 void
4918 GetMoveListEvent ()
4919 {
4920     char buf[MSG_SIZ];
4921     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4922         ics_getting_history = H_REQUESTED;
4923         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4924         SendToICS(buf);
4925     }
4926 }
4927
4928 void
4929 SendToBoth (char *msg)
4930 {   // to make it easy to keep two engines in step in dual analysis
4931     SendToProgram(msg, &first);
4932     if(second.analyzing) SendToProgram(msg, &second);
4933 }
4934
4935 void
4936 AnalysisPeriodicEvent (int force)
4937 {
4938     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4939          && !force) || !appData.periodicUpdates)
4940       return;
4941
4942     /* Send . command to Crafty to collect stats */
4943     SendToBoth(".\n");
4944
4945     /* Don't send another until we get a response (this makes
4946        us stop sending to old Crafty's which don't understand
4947        the "." command (sending illegal cmds resets node count & time,
4948        which looks bad)) */
4949     programStats.ok_to_send = 0;
4950 }
4951
4952 void
4953 ics_update_width (int new_width)
4954 {
4955         ics_printf("set width %d\n", new_width);
4956 }
4957
4958 void
4959 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4960 {
4961     char buf[MSG_SIZ];
4962
4963     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4964         // null move in variant where engine does not understand it (for analysis purposes)
4965         SendBoard(cps, moveNum + 1); // send position after move in stead.
4966         return;
4967     }
4968     if (cps->useUsermove) {
4969       SendToProgram("usermove ", cps);
4970     }
4971     if (cps->useSAN) {
4972       char *space;
4973       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4974         int len = space - parseList[moveNum];
4975         memcpy(buf, parseList[moveNum], len);
4976         buf[len++] = '\n';
4977         buf[len] = NULLCHAR;
4978       } else {
4979         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4980       }
4981       SendToProgram(buf, cps);
4982     } else {
4983       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4984         AlphaRank(moveList[moveNum], 4);
4985         SendToProgram(moveList[moveNum], cps);
4986         AlphaRank(moveList[moveNum], 4); // and back
4987       } else
4988       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4989        * the engine. It would be nice to have a better way to identify castle
4990        * moves here. */
4991       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4992                                                                          && cps->useOOCastle) {
4993         int fromX = moveList[moveNum][0] - AAA;
4994         int fromY = moveList[moveNum][1] - ONE;
4995         int toX = moveList[moveNum][2] - AAA;
4996         int toY = moveList[moveNum][3] - ONE;
4997         if((boards[moveNum][fromY][fromX] == WhiteKing
4998             && boards[moveNum][toY][toX] == WhiteRook)
4999            || (boards[moveNum][fromY][fromX] == BlackKing
5000                && boards[moveNum][toY][toX] == BlackRook)) {
5001           if(toX > fromX) SendToProgram("O-O\n", cps);
5002           else SendToProgram("O-O-O\n", cps);
5003         }
5004         else SendToProgram(moveList[moveNum], cps);
5005       } else
5006       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5007         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5008           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5009           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5010                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5011         } else
5012           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5013                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5014         SendToProgram(buf, cps);
5015       }
5016       else SendToProgram(moveList[moveNum], cps);
5017       /* End of additions by Tord */
5018     }
5019
5020     /* [HGM] setting up the opening has brought engine in force mode! */
5021     /*       Send 'go' if we are in a mode where machine should play. */
5022     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5023         (gameMode == TwoMachinesPlay   ||
5024 #if ZIPPY
5025          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5026 #endif
5027          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5028         SendToProgram("go\n", cps);
5029   if (appData.debugMode) {
5030     fprintf(debugFP, "(extra)\n");
5031   }
5032     }
5033     setboardSpoiledMachineBlack = 0;
5034 }
5035
5036 void
5037 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5038 {
5039     char user_move[MSG_SIZ];
5040     char suffix[4];
5041
5042     if(gameInfo.variant == VariantSChess && promoChar) {
5043         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5044         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5045     } else suffix[0] = NULLCHAR;
5046
5047     switch (moveType) {
5048       default:
5049         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5050                 (int)moveType, fromX, fromY, toX, toY);
5051         DisplayError(user_move + strlen("say "), 0);
5052         break;
5053       case WhiteKingSideCastle:
5054       case BlackKingSideCastle:
5055       case WhiteQueenSideCastleWild:
5056       case BlackQueenSideCastleWild:
5057       /* PUSH Fabien */
5058       case WhiteHSideCastleFR:
5059       case BlackHSideCastleFR:
5060       /* POP Fabien */
5061         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5062         break;
5063       case WhiteQueenSideCastle:
5064       case BlackQueenSideCastle:
5065       case WhiteKingSideCastleWild:
5066       case BlackKingSideCastleWild:
5067       /* PUSH Fabien */
5068       case WhiteASideCastleFR:
5069       case BlackASideCastleFR:
5070       /* POP Fabien */
5071         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5072         break;
5073       case WhiteNonPromotion:
5074       case BlackNonPromotion:
5075         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5076         break;
5077       case WhitePromotion:
5078       case BlackPromotion:
5079         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5080           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5081                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5082                 PieceToChar(WhiteFerz));
5083         else if(gameInfo.variant == VariantGreat)
5084           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5085                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5086                 PieceToChar(WhiteMan));
5087         else
5088           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5089                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5090                 promoChar);
5091         break;
5092       case WhiteDrop:
5093       case BlackDrop:
5094       drop:
5095         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5096                  ToUpper(PieceToChar((ChessSquare) fromX)),
5097                  AAA + toX, ONE + toY);
5098         break;
5099       case IllegalMove:  /* could be a variant we don't quite understand */
5100         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5101       case NormalMove:
5102       case WhiteCapturesEnPassant:
5103       case BlackCapturesEnPassant:
5104         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5105                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5106         break;
5107     }
5108     SendToICS(user_move);
5109     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5110         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5111 }
5112
5113 void
5114 UploadGameEvent ()
5115 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5116     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5117     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5118     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5119       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5120       return;
5121     }
5122     if(gameMode != IcsExamining) { // is this ever not the case?
5123         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5124
5125         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5126           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5127         } else { // on FICS we must first go to general examine mode
5128           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5129         }
5130         if(gameInfo.variant != VariantNormal) {
5131             // try figure out wild number, as xboard names are not always valid on ICS
5132             for(i=1; i<=36; i++) {
5133               snprintf(buf, MSG_SIZ, "wild/%d", i);
5134                 if(StringToVariant(buf) == gameInfo.variant) break;
5135             }
5136             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5137             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5138             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5139         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5140         SendToICS(ics_prefix);
5141         SendToICS(buf);
5142         if(startedFromSetupPosition || backwardMostMove != 0) {
5143           fen = PositionToFEN(backwardMostMove, NULL);
5144           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5145             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5146             SendToICS(buf);
5147           } else { // FICS: everything has to set by separate bsetup commands
5148             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5149             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5150             SendToICS(buf);
5151             if(!WhiteOnMove(backwardMostMove)) {
5152                 SendToICS("bsetup tomove black\n");
5153             }
5154             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5155             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5156             SendToICS(buf);
5157             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5158             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5159             SendToICS(buf);
5160             i = boards[backwardMostMove][EP_STATUS];
5161             if(i >= 0) { // set e.p.
5162               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5163                 SendToICS(buf);
5164             }
5165             bsetup++;
5166           }
5167         }
5168       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5169             SendToICS("bsetup done\n"); // switch to normal examining.
5170     }
5171     for(i = backwardMostMove; i<last; i++) {
5172         char buf[20];
5173         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5174         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5175             int len = strlen(moveList[i]);
5176             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5177             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5178         }
5179         SendToICS(buf);
5180     }
5181     SendToICS(ics_prefix);
5182     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5183 }
5184
5185 void
5186 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5187 {
5188     if (rf == DROP_RANK) {
5189       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5190       sprintf(move, "%c@%c%c\n",
5191                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5192     } else {
5193         if (promoChar == 'x' || promoChar == NULLCHAR) {
5194           sprintf(move, "%c%c%c%c\n",
5195                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5196         } else {
5197             sprintf(move, "%c%c%c%c%c\n",
5198                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5199         }
5200     }
5201 }
5202
5203 void
5204 ProcessICSInitScript (FILE *f)
5205 {
5206     char buf[MSG_SIZ];
5207
5208     while (fgets(buf, MSG_SIZ, f)) {
5209         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5210     }
5211
5212     fclose(f);
5213 }
5214
5215
5216 static int lastX, lastY, selectFlag, dragging;
5217
5218 void
5219 Sweep (int step)
5220 {
5221     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5222     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5223     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5224     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5225     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5226     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5227     do {
5228         promoSweep -= step;
5229         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5230         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5231         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5232         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5233         if(!step) step = -1;
5234     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5235             appData.testLegality && (promoSweep == king ||
5236             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5237     if(toX >= 0) {
5238         int victim = boards[currentMove][toY][toX];
5239         boards[currentMove][toY][toX] = promoSweep;
5240         DrawPosition(FALSE, boards[currentMove]);
5241         boards[currentMove][toY][toX] = victim;
5242     } else
5243     ChangeDragPiece(promoSweep);
5244 }
5245
5246 int
5247 PromoScroll (int x, int y)
5248 {
5249   int step = 0;
5250
5251   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5252   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5253   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5254   if(!step) return FALSE;
5255   lastX = x; lastY = y;
5256   if((promoSweep < BlackPawn) == flipView) step = -step;
5257   if(step > 0) selectFlag = 1;
5258   if(!selectFlag) Sweep(step);
5259   return FALSE;
5260 }
5261
5262 void
5263 NextPiece (int step)
5264 {
5265     ChessSquare piece = boards[currentMove][toY][toX];
5266     do {
5267         pieceSweep -= step;
5268         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5269         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5270         if(!step) step = -1;
5271     } while(PieceToChar(pieceSweep) == '.');
5272     boards[currentMove][toY][toX] = pieceSweep;
5273     DrawPosition(FALSE, boards[currentMove]);
5274     boards[currentMove][toY][toX] = piece;
5275 }
5276 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5277 void
5278 AlphaRank (char *move, int n)
5279 {
5280 //    char *p = move, c; int x, y;
5281
5282     if (appData.debugMode) {
5283         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5284     }
5285
5286     if(move[1]=='*' &&
5287        move[2]>='0' && move[2]<='9' &&
5288        move[3]>='a' && move[3]<='x'    ) {
5289         move[1] = '@';
5290         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5291         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5292     } else
5293     if(move[0]>='0' && move[0]<='9' &&
5294        move[1]>='a' && move[1]<='x' &&
5295        move[2]>='0' && move[2]<='9' &&
5296        move[3]>='a' && move[3]<='x'    ) {
5297         /* input move, Shogi -> normal */
5298         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5299         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5300         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5301         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5302     } else
5303     if(move[1]=='@' &&
5304        move[3]>='0' && move[3]<='9' &&
5305        move[2]>='a' && move[2]<='x'    ) {
5306         move[1] = '*';
5307         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5308         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5309     } else
5310     if(
5311        move[0]>='a' && move[0]<='x' &&
5312        move[3]>='0' && move[3]<='9' &&
5313        move[2]>='a' && move[2]<='x'    ) {
5314          /* output move, normal -> Shogi */
5315         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5316         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5317         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5318         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5319         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5320     }
5321     if (appData.debugMode) {
5322         fprintf(debugFP, "   out = '%s'\n", move);
5323     }
5324 }
5325
5326 char yy_textstr[8000];
5327
5328 /* Parser for moves from gnuchess, ICS, or user typein box */
5329 Boolean
5330 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5331 {
5332     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5333
5334     switch (*moveType) {
5335       case WhitePromotion:
5336       case BlackPromotion:
5337       case WhiteNonPromotion:
5338       case BlackNonPromotion:
5339       case NormalMove:
5340       case WhiteCapturesEnPassant:
5341       case BlackCapturesEnPassant:
5342       case WhiteKingSideCastle:
5343       case WhiteQueenSideCastle:
5344       case BlackKingSideCastle:
5345       case BlackQueenSideCastle:
5346       case WhiteKingSideCastleWild:
5347       case WhiteQueenSideCastleWild:
5348       case BlackKingSideCastleWild:
5349       case BlackQueenSideCastleWild:
5350       /* Code added by Tord: */
5351       case WhiteHSideCastleFR:
5352       case WhiteASideCastleFR:
5353       case BlackHSideCastleFR:
5354       case BlackASideCastleFR:
5355       /* End of code added by Tord */
5356       case IllegalMove:         /* bug or odd chess variant */
5357         *fromX = currentMoveString[0] - AAA;
5358         *fromY = currentMoveString[1] - ONE;
5359         *toX = currentMoveString[2] - AAA;
5360         *toY = currentMoveString[3] - ONE;
5361         *promoChar = currentMoveString[4];
5362         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5363             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5364     if (appData.debugMode) {
5365         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5366     }
5367             *fromX = *fromY = *toX = *toY = 0;
5368             return FALSE;
5369         }
5370         if (appData.testLegality) {
5371           return (*moveType != IllegalMove);
5372         } else {
5373           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5374                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5375         }
5376
5377       case WhiteDrop:
5378       case BlackDrop:
5379         *fromX = *moveType == WhiteDrop ?
5380           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5381           (int) CharToPiece(ToLower(currentMoveString[0]));
5382         *fromY = DROP_RANK;
5383         *toX = currentMoveString[2] - AAA;
5384         *toY = currentMoveString[3] - ONE;
5385         *promoChar = NULLCHAR;
5386         return TRUE;
5387
5388       case AmbiguousMove:
5389       case ImpossibleMove:
5390       case EndOfFile:
5391       case ElapsedTime:
5392       case Comment:
5393       case PGNTag:
5394       case NAG:
5395       case WhiteWins:
5396       case BlackWins:
5397       case GameIsDrawn:
5398       default:
5399     if (appData.debugMode) {
5400         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5401     }
5402         /* bug? */
5403         *fromX = *fromY = *toX = *toY = 0;
5404         *promoChar = NULLCHAR;
5405         return FALSE;
5406     }
5407 }
5408
5409 Boolean pushed = FALSE;
5410 char *lastParseAttempt;
5411
5412 void
5413 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5414 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5415   int fromX, fromY, toX, toY; char promoChar;
5416   ChessMove moveType;
5417   Boolean valid;
5418   int nr = 0;
5419
5420   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5421   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5422     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5423     pushed = TRUE;
5424   }
5425   endPV = forwardMostMove;
5426   do {
5427     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5428     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5429     lastParseAttempt = pv;
5430     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5431     if(!valid && nr == 0 &&
5432        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5433         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5434         // Hande case where played move is different from leading PV move
5435         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5436         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5437         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5438         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5439           endPV += 2; // if position different, keep this
5440           moveList[endPV-1][0] = fromX + AAA;
5441           moveList[endPV-1][1] = fromY + ONE;
5442           moveList[endPV-1][2] = toX + AAA;
5443           moveList[endPV-1][3] = toY + ONE;
5444           parseList[endPV-1][0] = NULLCHAR;
5445           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5446         }
5447       }
5448     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5449     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5450     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5451     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5452         valid++; // allow comments in PV
5453         continue;
5454     }
5455     nr++;
5456     if(endPV+1 > framePtr) break; // no space, truncate
5457     if(!valid) break;
5458     endPV++;
5459     CopyBoard(boards[endPV], boards[endPV-1]);
5460     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5461     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5462     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5463     CoordsToAlgebraic(boards[endPV - 1],
5464                              PosFlags(endPV - 1),
5465                              fromY, fromX, toY, toX, promoChar,
5466                              parseList[endPV - 1]);
5467   } while(valid);
5468   if(atEnd == 2) return; // used hidden, for PV conversion
5469   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5470   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5471   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5472                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5473   DrawPosition(TRUE, boards[currentMove]);
5474 }
5475
5476 int
5477 MultiPV (ChessProgramState *cps)
5478 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5479         int i;
5480         for(i=0; i<cps->nrOptions; i++)
5481             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5482                 return i;
5483         return -1;
5484 }
5485
5486 Boolean
5487 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5488 {
5489         int startPV, multi, lineStart, origIndex = index;
5490         char *p, buf2[MSG_SIZ];
5491         ChessProgramState *cps = (pane ? &second : &first);
5492
5493         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5494         lastX = x; lastY = y;
5495         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5496         lineStart = startPV = index;
5497         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5498         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5499         index = startPV;
5500         do{ while(buf[index] && buf[index] != '\n') index++;
5501         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5502         buf[index] = 0;
5503         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5504                 int n = cps->option[multi].value;
5505                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5506                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5507                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5508                 cps->option[multi].value = n;
5509                 *start = *end = 0;
5510                 return FALSE;
5511         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5512                 ExcludeClick(origIndex - lineStart);
5513                 return FALSE;
5514         }
5515         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5516         *start = startPV; *end = index-1;
5517         return TRUE;
5518 }
5519
5520 char *
5521 PvToSAN (char *pv)
5522 {
5523         static char buf[10*MSG_SIZ];
5524         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5525         *buf = NULLCHAR;
5526         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5527         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5528         for(i = forwardMostMove; i<endPV; i++){
5529             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5530             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5531             k += strlen(buf+k);
5532         }
5533         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5534         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5535         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5536         endPV = savedEnd;
5537         return buf;
5538 }
5539
5540 Boolean
5541 LoadPV (int x, int y)
5542 { // called on right mouse click to load PV
5543   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5544   lastX = x; lastY = y;
5545   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5546   return TRUE;
5547 }
5548
5549 void
5550 UnLoadPV ()
5551 {
5552   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5553   if(endPV < 0) return;
5554   if(appData.autoCopyPV) CopyFENToClipboard();
5555   endPV = -1;
5556   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5557         Boolean saveAnimate = appData.animate;
5558         if(pushed) {
5559             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5560                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5561             } else storedGames--; // abandon shelved tail of original game
5562         }
5563         pushed = FALSE;
5564         forwardMostMove = currentMove;
5565         currentMove = oldFMM;
5566         appData.animate = FALSE;
5567         ToNrEvent(forwardMostMove);
5568         appData.animate = saveAnimate;
5569   }
5570   currentMove = forwardMostMove;
5571   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5572   ClearPremoveHighlights();
5573   DrawPosition(TRUE, boards[currentMove]);
5574 }
5575
5576 void
5577 MovePV (int x, int y, int h)
5578 { // step through PV based on mouse coordinates (called on mouse move)
5579   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5580
5581   // we must somehow check if right button is still down (might be released off board!)
5582   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5583   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5584   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5585   if(!step) return;
5586   lastX = x; lastY = y;
5587
5588   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5589   if(endPV < 0) return;
5590   if(y < margin) step = 1; else
5591   if(y > h - margin) step = -1;
5592   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5593   currentMove += step;
5594   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5595   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5596                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5597   DrawPosition(FALSE, boards[currentMove]);
5598 }
5599
5600
5601 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5602 // All positions will have equal probability, but the current method will not provide a unique
5603 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5604 #define DARK 1
5605 #define LITE 2
5606 #define ANY 3
5607
5608 int squaresLeft[4];
5609 int piecesLeft[(int)BlackPawn];
5610 int seed, nrOfShuffles;
5611
5612 void
5613 GetPositionNumber ()
5614 {       // sets global variable seed
5615         int i;
5616
5617         seed = appData.defaultFrcPosition;
5618         if(seed < 0) { // randomize based on time for negative FRC position numbers
5619                 for(i=0; i<50; i++) seed += random();
5620                 seed = random() ^ random() >> 8 ^ random() << 8;
5621                 if(seed<0) seed = -seed;
5622         }
5623 }
5624
5625 int
5626 put (Board board, int pieceType, int rank, int n, int shade)
5627 // put the piece on the (n-1)-th empty squares of the given shade
5628 {
5629         int i;
5630
5631         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5632                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5633                         board[rank][i] = (ChessSquare) pieceType;
5634                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5635                         squaresLeft[ANY]--;
5636                         piecesLeft[pieceType]--;
5637                         return i;
5638                 }
5639         }
5640         return -1;
5641 }
5642
5643
5644 void
5645 AddOnePiece (Board board, int pieceType, int rank, int shade)
5646 // calculate where the next piece goes, (any empty square), and put it there
5647 {
5648         int i;
5649
5650         i = seed % squaresLeft[shade];
5651         nrOfShuffles *= squaresLeft[shade];
5652         seed /= squaresLeft[shade];
5653         put(board, pieceType, rank, i, shade);
5654 }
5655
5656 void
5657 AddTwoPieces (Board board, int pieceType, int rank)
5658 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5659 {
5660         int i, n=squaresLeft[ANY], j=n-1, k;
5661
5662         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5663         i = seed % k;  // pick one
5664         nrOfShuffles *= k;
5665         seed /= k;
5666         while(i >= j) i -= j--;
5667         j = n - 1 - j; i += j;
5668         put(board, pieceType, rank, j, ANY);
5669         put(board, pieceType, rank, i, ANY);
5670 }
5671
5672 void
5673 SetUpShuffle (Board board, int number)
5674 {
5675         int i, p, first=1;
5676
5677         GetPositionNumber(); nrOfShuffles = 1;
5678
5679         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5680         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5681         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5682
5683         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5684
5685         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5686             p = (int) board[0][i];
5687             if(p < (int) BlackPawn) piecesLeft[p] ++;
5688             board[0][i] = EmptySquare;
5689         }
5690
5691         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5692             // shuffles restricted to allow normal castling put KRR first
5693             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5694                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5695             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5696                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5697             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5698                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5699             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5700                 put(board, WhiteRook, 0, 0, ANY);
5701             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5702         }
5703
5704         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5705             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5706             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5707                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5708                 while(piecesLeft[p] >= 2) {
5709                     AddOnePiece(board, p, 0, LITE);
5710                     AddOnePiece(board, p, 0, DARK);
5711                 }
5712                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5713             }
5714
5715         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5716             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5717             // but we leave King and Rooks for last, to possibly obey FRC restriction
5718             if(p == (int)WhiteRook) continue;
5719             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5720             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5721         }
5722
5723         // now everything is placed, except perhaps King (Unicorn) and Rooks
5724
5725         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5726             // Last King gets castling rights
5727             while(piecesLeft[(int)WhiteUnicorn]) {
5728                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5729                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5730             }
5731
5732             while(piecesLeft[(int)WhiteKing]) {
5733                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5734                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5735             }
5736
5737
5738         } else {
5739             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5740             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5741         }
5742
5743         // Only Rooks can be left; simply place them all
5744         while(piecesLeft[(int)WhiteRook]) {
5745                 i = put(board, WhiteRook, 0, 0, ANY);
5746                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5747                         if(first) {
5748                                 first=0;
5749                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5750                         }
5751                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5752                 }
5753         }
5754         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5755             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5756         }
5757
5758         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5759 }
5760
5761 int
5762 SetCharTable (char *table, const char * map)
5763 /* [HGM] moved here from winboard.c because of its general usefulness */
5764 /*       Basically a safe strcpy that uses the last character as King */
5765 {
5766     int result = FALSE; int NrPieces;
5767
5768     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5769                     && NrPieces >= 12 && !(NrPieces&1)) {
5770         int i; /* [HGM] Accept even length from 12 to 34 */
5771
5772         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5773         for( i=0; i<NrPieces/2-1; i++ ) {
5774             table[i] = map[i];
5775             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5776         }
5777         table[(int) WhiteKing]  = map[NrPieces/2-1];
5778         table[(int) BlackKing]  = map[NrPieces-1];
5779
5780         result = TRUE;
5781     }
5782
5783     return result;
5784 }
5785
5786 void
5787 Prelude (Board board)
5788 {       // [HGM] superchess: random selection of exo-pieces
5789         int i, j, k; ChessSquare p;
5790         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5791
5792         GetPositionNumber(); // use FRC position number
5793
5794         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5795             SetCharTable(pieceToChar, appData.pieceToCharTable);
5796             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5797                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5798         }
5799
5800         j = seed%4;                 seed /= 4;
5801         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5802         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5803         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5804         j = seed%3 + (seed%3 >= j); seed /= 3;
5805         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5806         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5807         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5808         j = seed%3;                 seed /= 3;
5809         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5810         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5811         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5812         j = seed%2 + (seed%2 >= j); seed /= 2;
5813         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5814         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5815         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5816         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5817         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5818         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5819         put(board, exoPieces[0],    0, 0, ANY);
5820         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5821 }
5822
5823 void
5824 InitPosition (int redraw)
5825 {
5826     ChessSquare (* pieces)[BOARD_FILES];
5827     int i, j, pawnRow, overrule,
5828     oldx = gameInfo.boardWidth,
5829     oldy = gameInfo.boardHeight,
5830     oldh = gameInfo.holdingsWidth;
5831     static int oldv;
5832
5833     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5834
5835     /* [AS] Initialize pv info list [HGM] and game status */
5836     {
5837         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5838             pvInfoList[i].depth = 0;
5839             boards[i][EP_STATUS] = EP_NONE;
5840             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5841         }
5842
5843         initialRulePlies = 0; /* 50-move counter start */
5844
5845         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5846         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5847     }
5848
5849
5850     /* [HGM] logic here is completely changed. In stead of full positions */
5851     /* the initialized data only consist of the two backranks. The switch */
5852     /* selects which one we will use, which is than copied to the Board   */
5853     /* initialPosition, which for the rest is initialized by Pawns and    */
5854     /* empty squares. This initial position is then copied to boards[0],  */
5855     /* possibly after shuffling, so that it remains available.            */
5856
5857     gameInfo.holdingsWidth = 0; /* default board sizes */
5858     gameInfo.boardWidth    = 8;
5859     gameInfo.boardHeight   = 8;
5860     gameInfo.holdingsSize  = 0;
5861     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5862     for(i=0; i<BOARD_FILES-2; i++)
5863       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5864     initialPosition[EP_STATUS] = EP_NONE;
5865     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5866     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5867          SetCharTable(pieceNickName, appData.pieceNickNames);
5868     else SetCharTable(pieceNickName, "............");
5869     pieces = FIDEArray;
5870
5871     switch (gameInfo.variant) {
5872     case VariantFischeRandom:
5873       shuffleOpenings = TRUE;
5874     default:
5875       break;
5876     case VariantShatranj:
5877       pieces = ShatranjArray;
5878       nrCastlingRights = 0;
5879       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5880       break;
5881     case VariantMakruk:
5882       pieces = makrukArray;
5883       nrCastlingRights = 0;
5884       startedFromSetupPosition = TRUE;
5885       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5886       break;
5887     case VariantTwoKings:
5888       pieces = twoKingsArray;
5889       break;
5890     case VariantGrand:
5891       pieces = GrandArray;
5892       nrCastlingRights = 0;
5893       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5894       gameInfo.boardWidth = 10;
5895       gameInfo.boardHeight = 10;
5896       gameInfo.holdingsSize = 7;
5897       break;
5898     case VariantCapaRandom:
5899       shuffleOpenings = TRUE;
5900     case VariantCapablanca:
5901       pieces = CapablancaArray;
5902       gameInfo.boardWidth = 10;
5903       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5904       break;
5905     case VariantGothic:
5906       pieces = GothicArray;
5907       gameInfo.boardWidth = 10;
5908       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5909       break;
5910     case VariantSChess:
5911       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5912       gameInfo.holdingsSize = 7;
5913       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5914       break;
5915     case VariantJanus:
5916       pieces = JanusArray;
5917       gameInfo.boardWidth = 10;
5918       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5919       nrCastlingRights = 6;
5920         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5921         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5922         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5923         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5924         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5925         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5926       break;
5927     case VariantFalcon:
5928       pieces = FalconArray;
5929       gameInfo.boardWidth = 10;
5930       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5931       break;
5932     case VariantXiangqi:
5933       pieces = XiangqiArray;
5934       gameInfo.boardWidth  = 9;
5935       gameInfo.boardHeight = 10;
5936       nrCastlingRights = 0;
5937       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5938       break;
5939     case VariantShogi:
5940       pieces = ShogiArray;
5941       gameInfo.boardWidth  = 9;
5942       gameInfo.boardHeight = 9;
5943       gameInfo.holdingsSize = 7;
5944       nrCastlingRights = 0;
5945       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5946       break;
5947     case VariantCourier:
5948       pieces = CourierArray;
5949       gameInfo.boardWidth  = 12;
5950       nrCastlingRights = 0;
5951       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5952       break;
5953     case VariantKnightmate:
5954       pieces = KnightmateArray;
5955       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5956       break;
5957     case VariantSpartan:
5958       pieces = SpartanArray;
5959       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5960       break;
5961     case VariantFairy:
5962       pieces = fairyArray;
5963       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5964       break;
5965     case VariantGreat:
5966       pieces = GreatArray;
5967       gameInfo.boardWidth = 10;
5968       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5969       gameInfo.holdingsSize = 8;
5970       break;
5971     case VariantSuper:
5972       pieces = FIDEArray;
5973       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5974       gameInfo.holdingsSize = 8;
5975       startedFromSetupPosition = TRUE;
5976       break;
5977     case VariantCrazyhouse:
5978     case VariantBughouse:
5979       pieces = FIDEArray;
5980       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5981       gameInfo.holdingsSize = 5;
5982       break;
5983     case VariantWildCastle:
5984       pieces = FIDEArray;
5985       /* !!?shuffle with kings guaranteed to be on d or e file */
5986       shuffleOpenings = 1;
5987       break;
5988     case VariantNoCastle:
5989       pieces = FIDEArray;
5990       nrCastlingRights = 0;
5991       /* !!?unconstrained back-rank shuffle */
5992       shuffleOpenings = 1;
5993       break;
5994     }
5995
5996     overrule = 0;
5997     if(appData.NrFiles >= 0) {
5998         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5999         gameInfo.boardWidth = appData.NrFiles;
6000     }
6001     if(appData.NrRanks >= 0) {
6002         gameInfo.boardHeight = appData.NrRanks;
6003     }
6004     if(appData.holdingsSize >= 0) {
6005         i = appData.holdingsSize;
6006         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6007         gameInfo.holdingsSize = i;
6008     }
6009     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6010     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6011         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6012
6013     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6014     if(pawnRow < 1) pawnRow = 1;
6015     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6016
6017     /* User pieceToChar list overrules defaults */
6018     if(appData.pieceToCharTable != NULL)
6019         SetCharTable(pieceToChar, appData.pieceToCharTable);
6020
6021     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6022
6023         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6024             s = (ChessSquare) 0; /* account holding counts in guard band */
6025         for( i=0; i<BOARD_HEIGHT; i++ )
6026             initialPosition[i][j] = s;
6027
6028         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6029         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6030         initialPosition[pawnRow][j] = WhitePawn;
6031         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6032         if(gameInfo.variant == VariantXiangqi) {
6033             if(j&1) {
6034                 initialPosition[pawnRow][j] =
6035                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6036                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6037                    initialPosition[2][j] = WhiteCannon;
6038                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6039                 }
6040             }
6041         }
6042         if(gameInfo.variant == VariantGrand) {
6043             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6044                initialPosition[0][j] = WhiteRook;
6045                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6046             }
6047         }
6048         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6049     }
6050     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6051
6052             j=BOARD_LEFT+1;
6053             initialPosition[1][j] = WhiteBishop;
6054             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6055             j=BOARD_RGHT-2;
6056             initialPosition[1][j] = WhiteRook;
6057             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6058     }
6059
6060     if( nrCastlingRights == -1) {
6061         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6062         /*       This sets default castling rights from none to normal corners   */
6063         /* Variants with other castling rights must set them themselves above    */
6064         nrCastlingRights = 6;
6065
6066         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6067         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6068         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6069         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6070         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6071         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6072      }
6073
6074      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6075      if(gameInfo.variant == VariantGreat) { // promotion commoners
6076         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6077         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6078         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6079         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6080      }
6081      if( gameInfo.variant == VariantSChess ) {
6082       initialPosition[1][0] = BlackMarshall;
6083       initialPosition[2][0] = BlackAngel;
6084       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6085       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6086       initialPosition[1][1] = initialPosition[2][1] =
6087       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6088      }
6089   if (appData.debugMode) {
6090     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6091   }
6092     if(shuffleOpenings) {
6093         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6094         startedFromSetupPosition = TRUE;
6095     }
6096     if(startedFromPositionFile) {
6097       /* [HGM] loadPos: use PositionFile for every new game */
6098       CopyBoard(initialPosition, filePosition);
6099       for(i=0; i<nrCastlingRights; i++)
6100           initialRights[i] = filePosition[CASTLING][i];
6101       startedFromSetupPosition = TRUE;
6102     }
6103
6104     CopyBoard(boards[0], initialPosition);
6105
6106     if(oldx != gameInfo.boardWidth ||
6107        oldy != gameInfo.boardHeight ||
6108        oldv != gameInfo.variant ||
6109        oldh != gameInfo.holdingsWidth
6110                                          )
6111             InitDrawingSizes(-2 ,0);
6112
6113     oldv = gameInfo.variant;
6114     if (redraw)
6115       DrawPosition(TRUE, boards[currentMove]);
6116 }
6117
6118 void
6119 SendBoard (ChessProgramState *cps, int moveNum)
6120 {
6121     char message[MSG_SIZ];
6122
6123     if (cps->useSetboard) {
6124       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6125       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6126       SendToProgram(message, cps);
6127       free(fen);
6128
6129     } else {
6130       ChessSquare *bp;
6131       int i, j, left=0, right=BOARD_WIDTH;
6132       /* Kludge to set black to move, avoiding the troublesome and now
6133        * deprecated "black" command.
6134        */
6135       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6136         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6137
6138       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6139
6140       SendToProgram("edit\n", cps);
6141       SendToProgram("#\n", cps);
6142       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6143         bp = &boards[moveNum][i][left];
6144         for (j = left; j < right; j++, bp++) {
6145           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6146           if ((int) *bp < (int) BlackPawn) {
6147             if(j == BOARD_RGHT+1)
6148                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6149             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6150             if(message[0] == '+' || message[0] == '~') {
6151               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6152                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6153                         AAA + j, ONE + i);
6154             }
6155             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6156                 message[1] = BOARD_RGHT   - 1 - j + '1';
6157                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6158             }
6159             SendToProgram(message, cps);
6160           }
6161         }
6162       }
6163
6164       SendToProgram("c\n", cps);
6165       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6166         bp = &boards[moveNum][i][left];
6167         for (j = left; j < right; j++, bp++) {
6168           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6169           if (((int) *bp != (int) EmptySquare)
6170               && ((int) *bp >= (int) BlackPawn)) {
6171             if(j == BOARD_LEFT-2)
6172                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6173             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6174                     AAA + j, ONE + i);
6175             if(message[0] == '+' || message[0] == '~') {
6176               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6177                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6178                         AAA + j, ONE + i);
6179             }
6180             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6181                 message[1] = BOARD_RGHT   - 1 - j + '1';
6182                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6183             }
6184             SendToProgram(message, cps);
6185           }
6186         }
6187       }
6188
6189       SendToProgram(".\n", cps);
6190     }
6191     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6192 }
6193
6194 char exclusionHeader[MSG_SIZ];
6195 int exCnt, excludePtr;
6196 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6197 static Exclusion excluTab[200];
6198 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6199
6200 static void
6201 WriteMap (int s)
6202 {
6203     int j;
6204     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6205     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6206 }
6207
6208 static void
6209 ClearMap ()
6210 {
6211     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6212     excludePtr = 24; exCnt = 0;
6213     WriteMap(0);
6214 }
6215
6216 static void
6217 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6218 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6219     char buf[2*MOVE_LEN], *p;
6220     Exclusion *e = excluTab;
6221     int i;
6222     for(i=0; i<exCnt; i++)
6223         if(e[i].ff == fromX && e[i].fr == fromY &&
6224            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6225     if(i == exCnt) { // was not in exclude list; add it
6226         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6227         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6228             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6229             return; // abort
6230         }
6231         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6232         excludePtr++; e[i].mark = excludePtr++;
6233         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6234         exCnt++;
6235     }
6236     exclusionHeader[e[i].mark] = state;
6237 }
6238
6239 static int
6240 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6241 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6242     char buf[MSG_SIZ];
6243     int j, k;
6244     ChessMove moveType;
6245     if((signed char)promoChar == -1) { // kludge to indicate best move
6246         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6247             return 1; // if unparsable, abort
6248     }
6249     // update exclusion map (resolving toggle by consulting existing state)
6250     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6251     j = k%8; k >>= 3;
6252     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6253     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6254          excludeMap[k] |=   1<<j;
6255     else excludeMap[k] &= ~(1<<j);
6256     // update header
6257     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6258     // inform engine
6259     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6260     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6261     SendToBoth(buf);
6262     return (state == '+');
6263 }
6264
6265 static void
6266 ExcludeClick (int index)
6267 {
6268     int i, j;
6269     Exclusion *e = excluTab;
6270     if(index < 25) { // none, best or tail clicked
6271         if(index < 13) { // none: include all
6272             WriteMap(0); // clear map
6273             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6274             SendToBoth("include all\n"); // and inform engine
6275         } else if(index > 18) { // tail
6276             if(exclusionHeader[19] == '-') { // tail was excluded
6277                 SendToBoth("include all\n");
6278                 WriteMap(0); // clear map completely
6279                 // now re-exclude selected moves
6280                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6281                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6282             } else { // tail was included or in mixed state
6283                 SendToBoth("exclude all\n");
6284                 WriteMap(0xFF); // fill map completely
6285                 // now re-include selected moves
6286                 j = 0; // count them
6287                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6288                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6289                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6290             }
6291         } else { // best
6292             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6293         }
6294     } else {
6295         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6296             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6297             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6298             break;
6299         }
6300     }
6301 }
6302
6303 ChessSquare
6304 DefaultPromoChoice (int white)
6305 {
6306     ChessSquare result;
6307     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6308         result = WhiteFerz; // no choice
6309     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6310         result= WhiteKing; // in Suicide Q is the last thing we want
6311     else if(gameInfo.variant == VariantSpartan)
6312         result = white ? WhiteQueen : WhiteAngel;
6313     else result = WhiteQueen;
6314     if(!white) result = WHITE_TO_BLACK result;
6315     return result;
6316 }
6317
6318 static int autoQueen; // [HGM] oneclick
6319
6320 int
6321 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6322 {
6323     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6324     /* [HGM] add Shogi promotions */
6325     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6326     ChessSquare piece;
6327     ChessMove moveType;
6328     Boolean premove;
6329
6330     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6331     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6332
6333     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6334       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6335         return FALSE;
6336
6337     piece = boards[currentMove][fromY][fromX];
6338     if(gameInfo.variant == VariantShogi) {
6339         promotionZoneSize = BOARD_HEIGHT/3;
6340         highestPromotingPiece = (int)WhiteFerz;
6341     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6342         promotionZoneSize = 3;
6343     }
6344
6345     // Treat Lance as Pawn when it is not representing Amazon
6346     if(gameInfo.variant != VariantSuper) {
6347         if(piece == WhiteLance) piece = WhitePawn; else
6348         if(piece == BlackLance) piece = BlackPawn;
6349     }
6350
6351     // next weed out all moves that do not touch the promotion zone at all
6352     if((int)piece >= BlackPawn) {
6353         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6354              return FALSE;
6355         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6356     } else {
6357         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6358            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6359     }
6360
6361     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6362
6363     // weed out mandatory Shogi promotions
6364     if(gameInfo.variant == VariantShogi) {
6365         if(piece >= BlackPawn) {
6366             if(toY == 0 && piece == BlackPawn ||
6367                toY == 0 && piece == BlackQueen ||
6368                toY <= 1 && piece == BlackKnight) {
6369                 *promoChoice = '+';
6370                 return FALSE;
6371             }
6372         } else {
6373             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6374                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6375                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6376                 *promoChoice = '+';
6377                 return FALSE;
6378             }
6379         }
6380     }
6381
6382     // weed out obviously illegal Pawn moves
6383     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6384         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6385         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6386         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6387         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6388         // note we are not allowed to test for valid (non-)capture, due to premove
6389     }
6390
6391     // we either have a choice what to promote to, or (in Shogi) whether to promote
6392     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6393         *promoChoice = PieceToChar(BlackFerz);  // no choice
6394         return FALSE;
6395     }
6396     // no sense asking what we must promote to if it is going to explode...
6397     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6398         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6399         return FALSE;
6400     }
6401     // give caller the default choice even if we will not make it
6402     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6403     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6404     if(        sweepSelect && gameInfo.variant != VariantGreat
6405                            && gameInfo.variant != VariantGrand
6406                            && gameInfo.variant != VariantSuper) return FALSE;
6407     if(autoQueen) return FALSE; // predetermined
6408
6409     // suppress promotion popup on illegal moves that are not premoves
6410     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6411               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6412     if(appData.testLegality && !premove) {
6413         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6414                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6415         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6416             return FALSE;
6417     }
6418
6419     return TRUE;
6420 }
6421
6422 int
6423 InPalace (int row, int column)
6424 {   /* [HGM] for Xiangqi */
6425     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6426          column < (BOARD_WIDTH + 4)/2 &&
6427          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6428     return FALSE;
6429 }
6430
6431 int
6432 PieceForSquare (int x, int y)
6433 {
6434   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6435      return -1;
6436   else
6437      return boards[currentMove][y][x];
6438 }
6439
6440 int
6441 OKToStartUserMove (int x, int y)
6442 {
6443     ChessSquare from_piece;
6444     int white_piece;
6445
6446     if (matchMode) return FALSE;
6447     if (gameMode == EditPosition) return TRUE;
6448
6449     if (x >= 0 && y >= 0)
6450       from_piece = boards[currentMove][y][x];
6451     else
6452       from_piece = EmptySquare;
6453
6454     if (from_piece == EmptySquare) return FALSE;
6455
6456     white_piece = (int)from_piece >= (int)WhitePawn &&
6457       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6458
6459     switch (gameMode) {
6460       case AnalyzeFile:
6461       case TwoMachinesPlay:
6462       case EndOfGame:
6463         return FALSE;
6464
6465       case IcsObserving:
6466       case IcsIdle:
6467         return FALSE;
6468
6469       case MachinePlaysWhite:
6470       case IcsPlayingBlack:
6471         if (appData.zippyPlay) return FALSE;
6472         if (white_piece) {
6473             DisplayMoveError(_("You are playing Black"));
6474             return FALSE;
6475         }
6476         break;
6477
6478       case MachinePlaysBlack:
6479       case IcsPlayingWhite:
6480         if (appData.zippyPlay) return FALSE;
6481         if (!white_piece) {
6482             DisplayMoveError(_("You are playing White"));
6483             return FALSE;
6484         }
6485         break;
6486
6487       case PlayFromGameFile:
6488             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6489       case EditGame:
6490         if (!white_piece && WhiteOnMove(currentMove)) {
6491             DisplayMoveError(_("It is White's turn"));
6492             return FALSE;
6493         }
6494         if (white_piece && !WhiteOnMove(currentMove)) {
6495             DisplayMoveError(_("It is Black's turn"));
6496             return FALSE;
6497         }
6498         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6499             /* Editing correspondence game history */
6500             /* Could disallow this or prompt for confirmation */
6501             cmailOldMove = -1;
6502         }
6503         break;
6504
6505       case BeginningOfGame:
6506         if (appData.icsActive) return FALSE;
6507         if (!appData.noChessProgram) {
6508             if (!white_piece) {
6509                 DisplayMoveError(_("You are playing White"));
6510                 return FALSE;
6511             }
6512         }
6513         break;
6514
6515       case Training:
6516         if (!white_piece && WhiteOnMove(currentMove)) {
6517             DisplayMoveError(_("It is White's turn"));
6518             return FALSE;
6519         }
6520         if (white_piece && !WhiteOnMove(currentMove)) {
6521             DisplayMoveError(_("It is Black's turn"));
6522             return FALSE;
6523         }
6524         break;
6525
6526       default:
6527       case IcsExamining:
6528         break;
6529     }
6530     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6531         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6532         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6533         && gameMode != AnalyzeFile && gameMode != Training) {
6534         DisplayMoveError(_("Displayed position is not current"));
6535         return FALSE;
6536     }
6537     return TRUE;
6538 }
6539
6540 Boolean
6541 OnlyMove (int *x, int *y, Boolean captures)
6542 {
6543     DisambiguateClosure cl;
6544     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6545     switch(gameMode) {
6546       case MachinePlaysBlack:
6547       case IcsPlayingWhite:
6548       case BeginningOfGame:
6549         if(!WhiteOnMove(currentMove)) return FALSE;
6550         break;
6551       case MachinePlaysWhite:
6552       case IcsPlayingBlack:
6553         if(WhiteOnMove(currentMove)) return FALSE;
6554         break;
6555       case EditGame:
6556         break;
6557       default:
6558         return FALSE;
6559     }
6560     cl.pieceIn = EmptySquare;
6561     cl.rfIn = *y;
6562     cl.ffIn = *x;
6563     cl.rtIn = -1;
6564     cl.ftIn = -1;
6565     cl.promoCharIn = NULLCHAR;
6566     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6567     if( cl.kind == NormalMove ||
6568         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6569         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6570         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6571       fromX = cl.ff;
6572       fromY = cl.rf;
6573       *x = cl.ft;
6574       *y = cl.rt;
6575       return TRUE;
6576     }
6577     if(cl.kind != ImpossibleMove) return FALSE;
6578     cl.pieceIn = EmptySquare;
6579     cl.rfIn = -1;
6580     cl.ffIn = -1;
6581     cl.rtIn = *y;
6582     cl.ftIn = *x;
6583     cl.promoCharIn = NULLCHAR;
6584     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6585     if( cl.kind == NormalMove ||
6586         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6587         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6588         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6589       fromX = cl.ff;
6590       fromY = cl.rf;
6591       *x = cl.ft;
6592       *y = cl.rt;
6593       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6594       return TRUE;
6595     }
6596     return FALSE;
6597 }
6598
6599 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6600 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6601 int lastLoadGameUseList = FALSE;
6602 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6603 ChessMove lastLoadGameStart = EndOfFile;
6604 int doubleClick;
6605
6606 void
6607 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6608 {
6609     ChessMove moveType;
6610     ChessSquare pup;
6611     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6612
6613     /* Check if the user is playing in turn.  This is complicated because we
6614        let the user "pick up" a piece before it is his turn.  So the piece he
6615        tried to pick up may have been captured by the time he puts it down!
6616        Therefore we use the color the user is supposed to be playing in this
6617        test, not the color of the piece that is currently on the starting
6618        square---except in EditGame mode, where the user is playing both
6619        sides; fortunately there the capture race can't happen.  (It can
6620        now happen in IcsExamining mode, but that's just too bad.  The user
6621        will get a somewhat confusing message in that case.)
6622        */
6623
6624     switch (gameMode) {
6625       case AnalyzeFile:
6626       case TwoMachinesPlay:
6627       case EndOfGame:
6628       case IcsObserving:
6629       case IcsIdle:
6630         /* We switched into a game mode where moves are not accepted,
6631            perhaps while the mouse button was down. */
6632         return;
6633
6634       case MachinePlaysWhite:
6635         /* User is moving for Black */
6636         if (WhiteOnMove(currentMove)) {
6637             DisplayMoveError(_("It is White's turn"));
6638             return;
6639         }
6640         break;
6641
6642       case MachinePlaysBlack:
6643         /* User is moving for White */
6644         if (!WhiteOnMove(currentMove)) {
6645             DisplayMoveError(_("It is Black's turn"));
6646             return;
6647         }
6648         break;
6649
6650       case PlayFromGameFile:
6651             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6652       case EditGame:
6653       case IcsExamining:
6654       case BeginningOfGame:
6655       case AnalyzeMode:
6656       case Training:
6657         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6658         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6659             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6660             /* User is moving for Black */
6661             if (WhiteOnMove(currentMove)) {
6662                 DisplayMoveError(_("It is White's turn"));
6663                 return;
6664             }
6665         } else {
6666             /* User is moving for White */
6667             if (!WhiteOnMove(currentMove)) {
6668                 DisplayMoveError(_("It is Black's turn"));
6669                 return;
6670             }
6671         }
6672         break;
6673
6674       case IcsPlayingBlack:
6675         /* User is moving for Black */
6676         if (WhiteOnMove(currentMove)) {
6677             if (!appData.premove) {
6678                 DisplayMoveError(_("It is White's turn"));
6679             } else if (toX >= 0 && toY >= 0) {
6680                 premoveToX = toX;
6681                 premoveToY = toY;
6682                 premoveFromX = fromX;
6683                 premoveFromY = fromY;
6684                 premovePromoChar = promoChar;
6685                 gotPremove = 1;
6686                 if (appData.debugMode)
6687                     fprintf(debugFP, "Got premove: fromX %d,"
6688                             "fromY %d, toX %d, toY %d\n",
6689                             fromX, fromY, toX, toY);
6690             }
6691             return;
6692         }
6693         break;
6694
6695       case IcsPlayingWhite:
6696         /* User is moving for White */
6697         if (!WhiteOnMove(currentMove)) {
6698             if (!appData.premove) {
6699                 DisplayMoveError(_("It is Black's turn"));
6700             } else if (toX >= 0 && toY >= 0) {
6701                 premoveToX = toX;
6702                 premoveToY = toY;
6703                 premoveFromX = fromX;
6704                 premoveFromY = fromY;
6705                 premovePromoChar = promoChar;
6706                 gotPremove = 1;
6707                 if (appData.debugMode)
6708                     fprintf(debugFP, "Got premove: fromX %d,"
6709                             "fromY %d, toX %d, toY %d\n",
6710                             fromX, fromY, toX, toY);
6711             }
6712             return;
6713         }
6714         break;
6715
6716       default:
6717         break;
6718
6719       case EditPosition:
6720         /* EditPosition, empty square, or different color piece;
6721            click-click move is possible */
6722         if (toX == -2 || toY == -2) {
6723             boards[0][fromY][fromX] = EmptySquare;
6724             DrawPosition(FALSE, boards[currentMove]);
6725             return;
6726         } else if (toX >= 0 && toY >= 0) {
6727             boards[0][toY][toX] = boards[0][fromY][fromX];
6728             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6729                 if(boards[0][fromY][0] != EmptySquare) {
6730                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6731                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6732                 }
6733             } else
6734             if(fromX == BOARD_RGHT+1) {
6735                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6736                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6737                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6738                 }
6739             } else
6740             boards[0][fromY][fromX] = gatingPiece;
6741             DrawPosition(FALSE, boards[currentMove]);
6742             return;
6743         }
6744         return;
6745     }
6746
6747     if(toX < 0 || toY < 0) return;
6748     pup = boards[currentMove][toY][toX];
6749
6750     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6751     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6752          if( pup != EmptySquare ) return;
6753          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6754            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6755                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6756            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6757            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6758            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6759            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6760          fromY = DROP_RANK;
6761     }
6762
6763     /* [HGM] always test for legality, to get promotion info */
6764     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6765                                          fromY, fromX, toY, toX, promoChar);
6766
6767     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6768
6769     /* [HGM] but possibly ignore an IllegalMove result */
6770     if (appData.testLegality) {
6771         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6772             DisplayMoveError(_("Illegal move"));
6773             return;
6774         }
6775     }
6776
6777     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6778         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6779              ClearPremoveHighlights(); // was included
6780         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6781         return;
6782     }
6783
6784     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6785 }
6786
6787 /* Common tail of UserMoveEvent and DropMenuEvent */
6788 int
6789 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6790 {
6791     char *bookHit = 0;
6792
6793     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6794         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6795         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6796         if(WhiteOnMove(currentMove)) {
6797             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6798         } else {
6799             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6800         }
6801     }
6802
6803     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6804        move type in caller when we know the move is a legal promotion */
6805     if(moveType == NormalMove && promoChar)
6806         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6807
6808     /* [HGM] <popupFix> The following if has been moved here from
6809        UserMoveEvent(). Because it seemed to belong here (why not allow
6810        piece drops in training games?), and because it can only be
6811        performed after it is known to what we promote. */
6812     if (gameMode == Training) {
6813       /* compare the move played on the board to the next move in the
6814        * game. If they match, display the move and the opponent's response.
6815        * If they don't match, display an error message.
6816        */
6817       int saveAnimate;
6818       Board testBoard;
6819       CopyBoard(testBoard, boards[currentMove]);
6820       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6821
6822       if (CompareBoards(testBoard, boards[currentMove+1])) {
6823         ForwardInner(currentMove+1);
6824
6825         /* Autoplay the opponent's response.
6826          * if appData.animate was TRUE when Training mode was entered,
6827          * the response will be animated.
6828          */
6829         saveAnimate = appData.animate;
6830         appData.animate = animateTraining;
6831         ForwardInner(currentMove+1);
6832         appData.animate = saveAnimate;
6833
6834         /* check for the end of the game */
6835         if (currentMove >= forwardMostMove) {
6836           gameMode = PlayFromGameFile;
6837           ModeHighlight();
6838           SetTrainingModeOff();
6839           DisplayInformation(_("End of game"));
6840         }
6841       } else {
6842         DisplayError(_("Incorrect move"), 0);
6843       }
6844       return 1;
6845     }
6846
6847   /* Ok, now we know that the move is good, so we can kill
6848      the previous line in Analysis Mode */
6849   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6850                                 && currentMove < forwardMostMove) {
6851     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6852     else forwardMostMove = currentMove;
6853   }
6854
6855   ClearMap();
6856
6857   /* If we need the chess program but it's dead, restart it */
6858   ResurrectChessProgram();
6859
6860   /* A user move restarts a paused game*/
6861   if (pausing)
6862     PauseEvent();
6863
6864   thinkOutput[0] = NULLCHAR;
6865
6866   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6867
6868   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6869     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6870     return 1;
6871   }
6872
6873   if (gameMode == BeginningOfGame) {
6874     if (appData.noChessProgram) {
6875       gameMode = EditGame;
6876       SetGameInfo();
6877     } else {
6878       char buf[MSG_SIZ];
6879       gameMode = MachinePlaysBlack;
6880       StartClocks();
6881       SetGameInfo();
6882       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6883       DisplayTitle(buf);
6884       if (first.sendName) {
6885         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6886         SendToProgram(buf, &first);
6887       }
6888       StartClocks();
6889     }
6890     ModeHighlight();
6891   }
6892
6893   /* Relay move to ICS or chess engine */
6894   if (appData.icsActive) {
6895     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6896         gameMode == IcsExamining) {
6897       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6898         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6899         SendToICS("draw ");
6900         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6901       }
6902       // also send plain move, in case ICS does not understand atomic claims
6903       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6904       ics_user_moved = 1;
6905     }
6906   } else {
6907     if (first.sendTime && (gameMode == BeginningOfGame ||
6908                            gameMode == MachinePlaysWhite ||
6909                            gameMode == MachinePlaysBlack)) {
6910       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6911     }
6912     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6913          // [HGM] book: if program might be playing, let it use book
6914         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6915         first.maybeThinking = TRUE;
6916     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6917         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6918         SendBoard(&first, currentMove+1);
6919         if(second.analyzing) {
6920             if(!second.useSetboard) SendToProgram("undo\n", &second);
6921             SendBoard(&second, currentMove+1);
6922         }
6923     } else {
6924         SendMoveToProgram(forwardMostMove-1, &first);
6925         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6926     }
6927     if (currentMove == cmailOldMove + 1) {
6928       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6929     }
6930   }
6931
6932   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6933
6934   switch (gameMode) {
6935   case EditGame:
6936     if(appData.testLegality)
6937     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6938     case MT_NONE:
6939     case MT_CHECK:
6940       break;
6941     case MT_CHECKMATE:
6942     case MT_STAINMATE:
6943       if (WhiteOnMove(currentMove)) {
6944         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6945       } else {
6946         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6947       }
6948       break;
6949     case MT_STALEMATE:
6950       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6951       break;
6952     }
6953     break;
6954
6955   case MachinePlaysBlack:
6956   case MachinePlaysWhite:
6957     /* disable certain menu options while machine is thinking */
6958     SetMachineThinkingEnables();
6959     break;
6960
6961   default:
6962     break;
6963   }
6964
6965   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6966   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6967
6968   if(bookHit) { // [HGM] book: simulate book reply
6969         static char bookMove[MSG_SIZ]; // a bit generous?
6970
6971         programStats.nodes = programStats.depth = programStats.time =
6972         programStats.score = programStats.got_only_move = 0;
6973         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6974
6975         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6976         strcat(bookMove, bookHit);
6977         HandleMachineMove(bookMove, &first);
6978   }
6979   return 1;
6980 }
6981
6982 void
6983 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6984 {
6985     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6986     Markers *m = (Markers *) closure;
6987     if(rf == fromY && ff == fromX)
6988         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6989                          || kind == WhiteCapturesEnPassant
6990                          || kind == BlackCapturesEnPassant);
6991     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6992 }
6993
6994 void
6995 MarkTargetSquares (int clear)
6996 {
6997   int x, y;
6998   if(clear) // no reason to ever suppress clearing
6999     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7000   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7001      !appData.testLegality || gameMode == EditPosition) return;
7002   if(!clear) {
7003     int capt = 0;
7004     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7005     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7006       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7007       if(capt)
7008       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7009     }
7010   }
7011   DrawPosition(FALSE, NULL);
7012 }
7013
7014 int
7015 Explode (Board board, int fromX, int fromY, int toX, int toY)
7016 {
7017     if(gameInfo.variant == VariantAtomic &&
7018        (board[toY][toX] != EmptySquare ||                     // capture?
7019         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7020                          board[fromY][fromX] == BlackPawn   )
7021       )) {
7022         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7023         return TRUE;
7024     }
7025     return FALSE;
7026 }
7027
7028 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7029
7030 int
7031 CanPromote (ChessSquare piece, int y)
7032 {
7033         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7034         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7035         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7036            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7037            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7038                                                   gameInfo.variant == VariantMakruk) return FALSE;
7039         return (piece == BlackPawn && y == 1 ||
7040                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7041                 piece == BlackLance && y == 1 ||
7042                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7043 }
7044
7045 void
7046 LeftClick (ClickType clickType, int xPix, int yPix)
7047 {
7048     int x, y;
7049     Boolean saveAnimate;
7050     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7051     char promoChoice = NULLCHAR;
7052     ChessSquare piece;
7053     static TimeMark lastClickTime, prevClickTime;
7054
7055     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7056
7057     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7058
7059     if (clickType == Press) ErrorPopDown();
7060
7061     x = EventToSquare(xPix, BOARD_WIDTH);
7062     y = EventToSquare(yPix, BOARD_HEIGHT);
7063     if (!flipView && y >= 0) {
7064         y = BOARD_HEIGHT - 1 - y;
7065     }
7066     if (flipView && x >= 0) {
7067         x = BOARD_WIDTH - 1 - x;
7068     }
7069
7070     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7071         defaultPromoChoice = promoSweep;
7072         promoSweep = EmptySquare;   // terminate sweep
7073         promoDefaultAltered = TRUE;
7074         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7075     }
7076
7077     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7078         if(clickType == Release) return; // ignore upclick of click-click destination
7079         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7080         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7081         if(gameInfo.holdingsWidth &&
7082                 (WhiteOnMove(currentMove)
7083                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7084                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7085             // click in right holdings, for determining promotion piece
7086             ChessSquare p = boards[currentMove][y][x];
7087             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7088             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7089             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7090                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7091                 fromX = fromY = -1;
7092                 return;
7093             }
7094         }
7095         DrawPosition(FALSE, boards[currentMove]);
7096         return;
7097     }
7098
7099     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7100     if(clickType == Press
7101             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7102               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7103               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7104         return;
7105
7106     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7107         // could be static click on premove from-square: abort premove
7108         gotPremove = 0;
7109         ClearPremoveHighlights();
7110     }
7111
7112     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7113         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7114
7115     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7116         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7117                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7118         defaultPromoChoice = DefaultPromoChoice(side);
7119     }
7120
7121     autoQueen = appData.alwaysPromoteToQueen;
7122
7123     if (fromX == -1) {
7124       int originalY = y;
7125       gatingPiece = EmptySquare;
7126       if (clickType != Press) {
7127         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7128             DragPieceEnd(xPix, yPix); dragging = 0;
7129             DrawPosition(FALSE, NULL);
7130         }
7131         return;
7132       }
7133       doubleClick = FALSE;
7134       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7135         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7136       }
7137       fromX = x; fromY = y; toX = toY = -1;
7138       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7139          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7140          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7141             /* First square */
7142             if (OKToStartUserMove(fromX, fromY)) {
7143                 second = 0;
7144                 MarkTargetSquares(0);
7145                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7146                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7147                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7148                     promoSweep = defaultPromoChoice;
7149                     selectFlag = 0; lastX = xPix; lastY = yPix;
7150                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7151                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7152                 }
7153                 if (appData.highlightDragging) {
7154                     SetHighlights(fromX, fromY, -1, -1);
7155                 } else {
7156                     ClearHighlights();
7157                 }
7158             } else fromX = fromY = -1;
7159             return;
7160         }
7161     }
7162
7163     /* fromX != -1 */
7164     if (clickType == Press && gameMode != EditPosition) {
7165         ChessSquare fromP;
7166         ChessSquare toP;
7167         int frc;
7168
7169         // ignore off-board to clicks
7170         if(y < 0 || x < 0) return;
7171
7172         /* Check if clicking again on the same color piece */
7173         fromP = boards[currentMove][fromY][fromX];
7174         toP = boards[currentMove][y][x];
7175         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7176         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7177              WhitePawn <= toP && toP <= WhiteKing &&
7178              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7179              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7180             (BlackPawn <= fromP && fromP <= BlackKing &&
7181              BlackPawn <= toP && toP <= BlackKing &&
7182              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7183              !(fromP == BlackKing && toP == BlackRook && frc))) {
7184             /* Clicked again on same color piece -- changed his mind */
7185             second = (x == fromX && y == fromY);
7186             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7187                 second = FALSE; // first double-click rather than scond click
7188                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7189             }
7190             promoDefaultAltered = FALSE;
7191             MarkTargetSquares(1);
7192            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7193             if (appData.highlightDragging) {
7194                 SetHighlights(x, y, -1, -1);
7195             } else {
7196                 ClearHighlights();
7197             }
7198             if (OKToStartUserMove(x, y)) {
7199                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7200                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7201                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7202                  gatingPiece = boards[currentMove][fromY][fromX];
7203                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7204                 fromX = x;
7205                 fromY = y; dragging = 1;
7206                 MarkTargetSquares(0);
7207                 DragPieceBegin(xPix, yPix, FALSE);
7208                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7209                     promoSweep = defaultPromoChoice;
7210                     selectFlag = 0; lastX = xPix; lastY = yPix;
7211                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7212                 }
7213             }
7214            }
7215            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7216            second = FALSE;
7217         }
7218         // ignore clicks on holdings
7219         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7220     }
7221
7222     if (clickType == Release && x == fromX && y == fromY) {
7223         DragPieceEnd(xPix, yPix); dragging = 0;
7224         if(clearFlag) {
7225             // a deferred attempt to click-click move an empty square on top of a piece
7226             boards[currentMove][y][x] = EmptySquare;
7227             ClearHighlights();
7228             DrawPosition(FALSE, boards[currentMove]);
7229             fromX = fromY = -1; clearFlag = 0;
7230             return;
7231         }
7232         if (appData.animateDragging) {
7233             /* Undo animation damage if any */
7234             DrawPosition(FALSE, NULL);
7235         }
7236         if (second || sweepSelecting) {
7237             /* Second up/down in same square; just abort move */
7238             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7239             second = sweepSelecting = 0;
7240             fromX = fromY = -1;
7241             gatingPiece = EmptySquare;
7242             ClearHighlights();
7243             gotPremove = 0;
7244             ClearPremoveHighlights();
7245         } else {
7246             /* First upclick in same square; start click-click mode */
7247             SetHighlights(x, y, -1, -1);
7248         }
7249         return;
7250     }
7251
7252     clearFlag = 0;
7253
7254     /* we now have a different from- and (possibly off-board) to-square */
7255     /* Completed move */
7256     if(!sweepSelecting) {
7257         toX = x;
7258         toY = y;
7259     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7260
7261     saveAnimate = appData.animate;
7262     if (clickType == Press) {
7263         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7264             // must be Edit Position mode with empty-square selected
7265             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7266             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7267             return;
7268         }
7269         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7270           if(appData.sweepSelect) {
7271             ChessSquare piece = boards[currentMove][fromY][fromX];
7272             promoSweep = defaultPromoChoice;
7273             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7274             selectFlag = 0; lastX = xPix; lastY = yPix;
7275             Sweep(0); // Pawn that is going to promote: preview promotion piece
7276             sweepSelecting = 1;
7277             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7278             MarkTargetSquares(1);
7279           }
7280           return; // promo popup appears on up-click
7281         }
7282         /* Finish clickclick move */
7283         if (appData.animate || appData.highlightLastMove) {
7284             SetHighlights(fromX, fromY, toX, toY);
7285         } else {
7286             ClearHighlights();
7287         }
7288     } else {
7289 #if 0
7290 // [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
7291         /* Finish drag move */
7292         if (appData.highlightLastMove) {
7293             SetHighlights(fromX, fromY, toX, toY);
7294         } else {
7295             ClearHighlights();
7296         }
7297 #endif
7298         DragPieceEnd(xPix, yPix); dragging = 0;
7299         /* Don't animate move and drag both */
7300         appData.animate = FALSE;
7301     }
7302
7303     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7304     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7305         ChessSquare piece = boards[currentMove][fromY][fromX];
7306         if(gameMode == EditPosition && piece != EmptySquare &&
7307            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7308             int n;
7309
7310             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7311                 n = PieceToNumber(piece - (int)BlackPawn);
7312                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7313                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7314                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7315             } else
7316             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7317                 n = PieceToNumber(piece);
7318                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7319                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7320                 boards[currentMove][n][BOARD_WIDTH-2]++;
7321             }
7322             boards[currentMove][fromY][fromX] = EmptySquare;
7323         }
7324         ClearHighlights();
7325         fromX = fromY = -1;
7326         MarkTargetSquares(1);
7327         DrawPosition(TRUE, boards[currentMove]);
7328         return;
7329     }
7330
7331     // off-board moves should not be highlighted
7332     if(x < 0 || y < 0) ClearHighlights();
7333
7334     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7335
7336     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7337         SetHighlights(fromX, fromY, toX, toY);
7338         MarkTargetSquares(1);
7339         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7340             // [HGM] super: promotion to captured piece selected from holdings
7341             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7342             promotionChoice = TRUE;
7343             // kludge follows to temporarily execute move on display, without promoting yet
7344             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7345             boards[currentMove][toY][toX] = p;
7346             DrawPosition(FALSE, boards[currentMove]);
7347             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7348             boards[currentMove][toY][toX] = q;
7349             DisplayMessage("Click in holdings to choose piece", "");
7350             return;
7351         }
7352         PromotionPopUp();
7353     } else {
7354         int oldMove = currentMove;
7355         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7356         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7357         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7358         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7359            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7360             DrawPosition(TRUE, boards[currentMove]);
7361         MarkTargetSquares(1);
7362         fromX = fromY = -1;
7363     }
7364     appData.animate = saveAnimate;
7365     if (appData.animate || appData.animateDragging) {
7366         /* Undo animation damage if needed */
7367         DrawPosition(FALSE, NULL);
7368     }
7369 }
7370
7371 int
7372 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7373 {   // front-end-free part taken out of PieceMenuPopup
7374     int whichMenu; int xSqr, ySqr;
7375
7376     if(seekGraphUp) { // [HGM] seekgraph
7377         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7378         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7379         return -2;
7380     }
7381
7382     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7383          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7384         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7385         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7386         if(action == Press)   {
7387             originalFlip = flipView;
7388             flipView = !flipView; // temporarily flip board to see game from partners perspective
7389             DrawPosition(TRUE, partnerBoard);
7390             DisplayMessage(partnerStatus, "");
7391             partnerUp = TRUE;
7392         } else if(action == Release) {
7393             flipView = originalFlip;
7394             DrawPosition(TRUE, boards[currentMove]);
7395             partnerUp = FALSE;
7396         }
7397         return -2;
7398     }
7399
7400     xSqr = EventToSquare(x, BOARD_WIDTH);
7401     ySqr = EventToSquare(y, BOARD_HEIGHT);
7402     if (action == Release) {
7403         if(pieceSweep != EmptySquare) {
7404             EditPositionMenuEvent(pieceSweep, toX, toY);
7405             pieceSweep = EmptySquare;
7406         } else UnLoadPV(); // [HGM] pv
7407     }
7408     if (action != Press) return -2; // return code to be ignored
7409     switch (gameMode) {
7410       case IcsExamining:
7411         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7412       case EditPosition:
7413         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7414         if (xSqr < 0 || ySqr < 0) return -1;
7415         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7416         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7417         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7418         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7419         NextPiece(0);
7420         return 2; // grab
7421       case IcsObserving:
7422         if(!appData.icsEngineAnalyze) return -1;
7423       case IcsPlayingWhite:
7424       case IcsPlayingBlack:
7425         if(!appData.zippyPlay) goto noZip;
7426       case AnalyzeMode:
7427       case AnalyzeFile:
7428       case MachinePlaysWhite:
7429       case MachinePlaysBlack:
7430       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7431         if (!appData.dropMenu) {
7432           LoadPV(x, y);
7433           return 2; // flag front-end to grab mouse events
7434         }
7435         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7436            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7437       case EditGame:
7438       noZip:
7439         if (xSqr < 0 || ySqr < 0) return -1;
7440         if (!appData.dropMenu || appData.testLegality &&
7441             gameInfo.variant != VariantBughouse &&
7442             gameInfo.variant != VariantCrazyhouse) return -1;
7443         whichMenu = 1; // drop menu
7444         break;
7445       default:
7446         return -1;
7447     }
7448
7449     if (((*fromX = xSqr) < 0) ||
7450         ((*fromY = ySqr) < 0)) {
7451         *fromX = *fromY = -1;
7452         return -1;
7453     }
7454     if (flipView)
7455       *fromX = BOARD_WIDTH - 1 - *fromX;
7456     else
7457       *fromY = BOARD_HEIGHT - 1 - *fromY;
7458
7459     return whichMenu;
7460 }
7461
7462 void
7463 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7464 {
7465 //    char * hint = lastHint;
7466     FrontEndProgramStats stats;
7467
7468     stats.which = cps == &first ? 0 : 1;
7469     stats.depth = cpstats->depth;
7470     stats.nodes = cpstats->nodes;
7471     stats.score = cpstats->score;
7472     stats.time = cpstats->time;
7473     stats.pv = cpstats->movelist;
7474     stats.hint = lastHint;
7475     stats.an_move_index = 0;
7476     stats.an_move_count = 0;
7477
7478     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7479         stats.hint = cpstats->move_name;
7480         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7481         stats.an_move_count = cpstats->nr_moves;
7482     }
7483
7484     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
7485
7486     SetProgramStats( &stats );
7487 }
7488
7489 void
7490 ClearEngineOutputPane (int which)
7491 {
7492     static FrontEndProgramStats dummyStats;
7493     dummyStats.which = which;
7494     dummyStats.pv = "#";
7495     SetProgramStats( &dummyStats );
7496 }
7497
7498 #define MAXPLAYERS 500
7499
7500 char *
7501 TourneyStandings (int display)
7502 {
7503     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7504     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7505     char result, *p, *names[MAXPLAYERS];
7506
7507     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7508         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7509     names[0] = p = strdup(appData.participants);
7510     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7511
7512     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7513
7514     while(result = appData.results[nr]) {
7515         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7516         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7517         wScore = bScore = 0;
7518         switch(result) {
7519           case '+': wScore = 2; break;
7520           case '-': bScore = 2; break;
7521           case '=': wScore = bScore = 1; break;
7522           case ' ':
7523           case '*': return strdup("busy"); // tourney not finished
7524         }
7525         score[w] += wScore;
7526         score[b] += bScore;
7527         games[w]++;
7528         games[b]++;
7529         nr++;
7530     }
7531     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7532     for(w=0; w<nPlayers; w++) {
7533         bScore = -1;
7534         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7535         ranking[w] = b; points[w] = bScore; score[b] = -2;
7536     }
7537     p = malloc(nPlayers*34+1);
7538     for(w=0; w<nPlayers && w<display; w++)
7539         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7540     free(names[0]);
7541     return p;
7542 }
7543
7544 void
7545 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7546 {       // count all piece types
7547         int p, f, r;
7548         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7549         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7550         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7551                 p = board[r][f];
7552                 pCnt[p]++;
7553                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7554                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7555                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7556                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7557                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7558                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7559         }
7560 }
7561
7562 int
7563 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7564 {
7565         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7566         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7567
7568         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7569         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7570         if(myPawns == 2 && nMine == 3) // KPP
7571             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7572         if(myPawns == 1 && nMine == 2) // KP
7573             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7574         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7575             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7576         if(myPawns) return FALSE;
7577         if(pCnt[WhiteRook+side])
7578             return pCnt[BlackRook-side] ||
7579                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7580                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7581                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7582         if(pCnt[WhiteCannon+side]) {
7583             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7584             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7585         }
7586         if(pCnt[WhiteKnight+side])
7587             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7588         return FALSE;
7589 }
7590
7591 int
7592 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7593 {
7594         VariantClass v = gameInfo.variant;
7595
7596         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7597         if(v == VariantShatranj) return TRUE; // always winnable through baring
7598         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7599         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7600
7601         if(v == VariantXiangqi) {
7602                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7603
7604                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7605                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7606                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7607                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7608                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7609                 if(stale) // we have at least one last-rank P plus perhaps C
7610                     return majors // KPKX
7611                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7612                 else // KCA*E*
7613                     return pCnt[WhiteFerz+side] // KCAK
7614                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7615                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7616                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7617
7618         } else if(v == VariantKnightmate) {
7619                 if(nMine == 1) return FALSE;
7620                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7621         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7622                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7623
7624                 if(nMine == 1) return FALSE; // bare King
7625                 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
7626                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7627                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7628                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7629                 if(pCnt[WhiteKnight+side])
7630                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7631                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7632                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7633                 if(nBishops)
7634                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7635                 if(pCnt[WhiteAlfil+side])
7636                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7637                 if(pCnt[WhiteWazir+side])
7638                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7639         }
7640
7641         return TRUE;
7642 }
7643
7644 int
7645 CompareWithRights (Board b1, Board b2)
7646 {
7647     int rights = 0;
7648     if(!CompareBoards(b1, b2)) return FALSE;
7649     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7650     /* compare castling rights */
7651     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7652            rights++; /* King lost rights, while rook still had them */
7653     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7654         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7655            rights++; /* but at least one rook lost them */
7656     }
7657     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7658            rights++;
7659     if( b1[CASTLING][5] != NoRights ) {
7660         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7661            rights++;
7662     }
7663     return rights == 0;
7664 }
7665
7666 int
7667 Adjudicate (ChessProgramState *cps)
7668 {       // [HGM] some adjudications useful with buggy engines
7669         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7670         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7671         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7672         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7673         int k, drop, count = 0; static int bare = 1;
7674         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7675         Boolean canAdjudicate = !appData.icsActive;
7676
7677         // most tests only when we understand the game, i.e. legality-checking on
7678             if( appData.testLegality )
7679             {   /* [HGM] Some more adjudications for obstinate engines */
7680                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7681                 static int moveCount = 6;
7682                 ChessMove result;
7683                 char *reason = NULL;
7684
7685                 /* Count what is on board. */
7686                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7687
7688                 /* Some material-based adjudications that have to be made before stalemate test */
7689                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7690                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7691                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7692                      if(canAdjudicate && appData.checkMates) {
7693                          if(engineOpponent)
7694                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7695                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7696                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7697                          return 1;
7698                      }
7699                 }
7700
7701                 /* Bare King in Shatranj (loses) or Losers (wins) */
7702                 if( nrW == 1 || nrB == 1) {
7703                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7704                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7705                      if(canAdjudicate && appData.checkMates) {
7706                          if(engineOpponent)
7707                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7708                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7709                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7710                          return 1;
7711                      }
7712                   } else
7713                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7714                   {    /* bare King */
7715                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7716                         if(canAdjudicate && appData.checkMates) {
7717                             /* but only adjudicate if adjudication enabled */
7718                             if(engineOpponent)
7719                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7720                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7721                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7722                             return 1;
7723                         }
7724                   }
7725                 } else bare = 1;
7726
7727
7728             // don't wait for engine to announce game end if we can judge ourselves
7729             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7730               case MT_CHECK:
7731                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7732                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7733                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7734                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7735                             checkCnt++;
7736                         if(checkCnt >= 2) {
7737                             reason = "Xboard adjudication: 3rd check";
7738                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7739                             break;
7740                         }
7741                     }
7742                 }
7743               case MT_NONE:
7744               default:
7745                 break;
7746               case MT_STALEMATE:
7747               case MT_STAINMATE:
7748                 reason = "Xboard adjudication: Stalemate";
7749                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7750                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7751                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7752                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7753                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7754                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7755                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7756                                                                         EP_CHECKMATE : EP_WINS);
7757                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7758                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7759                 }
7760                 break;
7761               case MT_CHECKMATE:
7762                 reason = "Xboard adjudication: Checkmate";
7763                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7764                 break;
7765             }
7766
7767                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7768                     case EP_STALEMATE:
7769                         result = GameIsDrawn; break;
7770                     case EP_CHECKMATE:
7771                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7772                     case EP_WINS:
7773                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7774                     default:
7775                         result = EndOfFile;
7776                 }
7777                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7778                     if(engineOpponent)
7779                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7780                     GameEnds( result, reason, GE_XBOARD );
7781                     return 1;
7782                 }
7783
7784                 /* Next absolutely insufficient mating material. */
7785                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7786                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7787                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7788
7789                      /* always flag draws, for judging claims */
7790                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7791
7792                      if(canAdjudicate && appData.materialDraws) {
7793                          /* but only adjudicate them if adjudication enabled */
7794                          if(engineOpponent) {
7795                            SendToProgram("force\n", engineOpponent); // suppress reply
7796                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7797                          }
7798                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7799                          return 1;
7800                      }
7801                 }
7802
7803                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7804                 if(gameInfo.variant == VariantXiangqi ?
7805                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7806                  : nrW + nrB == 4 &&
7807                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7808                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7809                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7810                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7811                    ) ) {
7812                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7813                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7814                           if(engineOpponent) {
7815                             SendToProgram("force\n", engineOpponent); // suppress reply
7816                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7817                           }
7818                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7819                           return 1;
7820                      }
7821                 } else moveCount = 6;
7822             }
7823
7824         // Repetition draws and 50-move rule can be applied independently of legality testing
7825
7826                 /* Check for rep-draws */
7827                 count = 0;
7828                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7829                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7830                 for(k = forwardMostMove-2;
7831                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7832                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7833                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7834                     k-=2)
7835                 {   int rights=0;
7836                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7837                         /* compare castling rights */
7838                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7839                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7840                                 rights++; /* King lost rights, while rook still had them */
7841                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7842                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7843                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7844                                    rights++; /* but at least one rook lost them */
7845                         }
7846                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7847                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7848                                 rights++;
7849                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7850                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7851                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7852                                    rights++;
7853                         }
7854                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7855                             && appData.drawRepeats > 1) {
7856                              /* adjudicate after user-specified nr of repeats */
7857                              int result = GameIsDrawn;
7858                              char *details = "XBoard adjudication: repetition draw";
7859                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7860                                 // [HGM] xiangqi: check for forbidden perpetuals
7861                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7862                                 for(m=forwardMostMove; m>k; m-=2) {
7863                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7864                                         ourPerpetual = 0; // the current mover did not always check
7865                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7866                                         hisPerpetual = 0; // the opponent did not always check
7867                                 }
7868                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7869                                                                         ourPerpetual, hisPerpetual);
7870                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7871                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7872                                     details = "Xboard adjudication: perpetual checking";
7873                                 } else
7874                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7875                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7876                                 } else
7877                                 // Now check for perpetual chases
7878                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7879                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7880                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7881                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7882                                         static char resdet[MSG_SIZ];
7883                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7884                                         details = resdet;
7885                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7886                                     } else
7887                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7888                                         break; // Abort repetition-checking loop.
7889                                 }
7890                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7891                              }
7892                              if(engineOpponent) {
7893                                SendToProgram("force\n", engineOpponent); // suppress reply
7894                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7895                              }
7896                              GameEnds( result, details, GE_XBOARD );
7897                              return 1;
7898                         }
7899                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7900                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7901                     }
7902                 }
7903
7904                 /* Now we test for 50-move draws. Determine ply count */
7905                 count = forwardMostMove;
7906                 /* look for last irreversble move */
7907                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7908                     count--;
7909                 /* if we hit starting position, add initial plies */
7910                 if( count == backwardMostMove )
7911                     count -= initialRulePlies;
7912                 count = forwardMostMove - count;
7913                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7914                         // adjust reversible move counter for checks in Xiangqi
7915                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7916                         if(i < backwardMostMove) i = backwardMostMove;
7917                         while(i <= forwardMostMove) {
7918                                 lastCheck = inCheck; // check evasion does not count
7919                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7920                                 if(inCheck || lastCheck) count--; // check does not count
7921                                 i++;
7922                         }
7923                 }
7924                 if( count >= 100)
7925                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7926                          /* this is used to judge if draw claims are legal */
7927                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7928                          if(engineOpponent) {
7929                            SendToProgram("force\n", engineOpponent); // suppress reply
7930                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7931                          }
7932                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7933                          return 1;
7934                 }
7935
7936                 /* if draw offer is pending, treat it as a draw claim
7937                  * when draw condition present, to allow engines a way to
7938                  * claim draws before making their move to avoid a race
7939                  * condition occurring after their move
7940                  */
7941                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7942                          char *p = NULL;
7943                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7944                              p = "Draw claim: 50-move rule";
7945                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7946                              p = "Draw claim: 3-fold repetition";
7947                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7948                              p = "Draw claim: insufficient mating material";
7949                          if( p != NULL && canAdjudicate) {
7950                              if(engineOpponent) {
7951                                SendToProgram("force\n", engineOpponent); // suppress reply
7952                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7953                              }
7954                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7955                              return 1;
7956                          }
7957                 }
7958
7959                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7960                     if(engineOpponent) {
7961                       SendToProgram("force\n", engineOpponent); // suppress reply
7962                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7963                     }
7964                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7965                     return 1;
7966                 }
7967         return 0;
7968 }
7969
7970 char *
7971 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7972 {   // [HGM] book: this routine intercepts moves to simulate book replies
7973     char *bookHit = NULL;
7974
7975     //first determine if the incoming move brings opponent into his book
7976     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7977         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7978     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7979     if(bookHit != NULL && !cps->bookSuspend) {
7980         // make sure opponent is not going to reply after receiving move to book position
7981         SendToProgram("force\n", cps);
7982         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7983     }
7984     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7985     // now arrange restart after book miss
7986     if(bookHit) {
7987         // after a book hit we never send 'go', and the code after the call to this routine
7988         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7989         char buf[MSG_SIZ], *move = bookHit;
7990         if(cps->useSAN) {
7991             int fromX, fromY, toX, toY;
7992             char promoChar;
7993             ChessMove moveType;
7994             move = buf + 30;
7995             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7996                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7997                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7998                                     PosFlags(forwardMostMove),
7999                                     fromY, fromX, toY, toX, promoChar, move);
8000             } else {
8001                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8002                 bookHit = NULL;
8003             }
8004         }
8005         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8006         SendToProgram(buf, cps);
8007         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8008     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8009         SendToProgram("go\n", cps);
8010         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8011     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8012         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8013             SendToProgram("go\n", cps);
8014         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8015     }
8016     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8017 }
8018
8019 int
8020 LoadError (char *errmess, ChessProgramState *cps)
8021 {   // unloads engine and switches back to -ncp mode if it was first
8022     if(cps->initDone) return FALSE;
8023     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8024     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8025     cps->pr = NoProc;
8026     if(cps == &first) {
8027         appData.noChessProgram = TRUE;
8028         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8029         gameMode = BeginningOfGame; ModeHighlight();
8030         SetNCPMode();
8031     }
8032     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8033     DisplayMessage("", ""); // erase waiting message
8034     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8035     return TRUE;
8036 }
8037
8038 char *savedMessage;
8039 ChessProgramState *savedState;
8040 void
8041 DeferredBookMove (void)
8042 {
8043         if(savedState->lastPing != savedState->lastPong)
8044                     ScheduleDelayedEvent(DeferredBookMove, 10);
8045         else
8046         HandleMachineMove(savedMessage, savedState);
8047 }
8048
8049 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8050 static ChessProgramState *stalledEngine;
8051 static char stashedInputMove[MSG_SIZ];
8052
8053 void
8054 HandleMachineMove (char *message, ChessProgramState *cps)
8055 {
8056     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8057     char realname[MSG_SIZ];
8058     int fromX, fromY, toX, toY;
8059     ChessMove moveType;
8060     char promoChar;
8061     char *p, *pv=buf1;
8062     int machineWhite, oldError;
8063     char *bookHit;
8064
8065     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8066         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8067         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8068             DisplayError(_("Invalid pairing from pairing engine"), 0);
8069             return;
8070         }
8071         pairingReceived = 1;
8072         NextMatchGame();
8073         return; // Skim the pairing messages here.
8074     }
8075
8076     oldError = cps->userError; cps->userError = 0;
8077
8078 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8079     /*
8080      * Kludge to ignore BEL characters
8081      */
8082     while (*message == '\007') message++;
8083
8084     /*
8085      * [HGM] engine debug message: ignore lines starting with '#' character
8086      */
8087     if(cps->debug && *message == '#') return;
8088
8089     /*
8090      * Look for book output
8091      */
8092     if (cps == &first && bookRequested) {
8093         if (message[0] == '\t' || message[0] == ' ') {
8094             /* Part of the book output is here; append it */
8095             strcat(bookOutput, message);
8096             strcat(bookOutput, "  \n");
8097             return;
8098         } else if (bookOutput[0] != NULLCHAR) {
8099             /* All of book output has arrived; display it */
8100             char *p = bookOutput;
8101             while (*p != NULLCHAR) {
8102                 if (*p == '\t') *p = ' ';
8103                 p++;
8104             }
8105             DisplayInformation(bookOutput);
8106             bookRequested = FALSE;
8107             /* Fall through to parse the current output */
8108         }
8109     }
8110
8111     /*
8112      * Look for machine move.
8113      */
8114     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8115         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8116     {
8117         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8118             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8119             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8120             stalledEngine = cps;
8121             if(appData.ponderNextMove) { // bring opponent out of ponder
8122                 if(gameMode == TwoMachinesPlay) {
8123                     if(cps->other->pause)
8124                         PauseEngine(cps->other);
8125                     else
8126                         SendToProgram("easy\n", cps->other);
8127                 }
8128             }
8129             StopClocks();
8130             return;
8131         }
8132
8133         /* This method is only useful on engines that support ping */
8134         if (cps->lastPing != cps->lastPong) {
8135           if (gameMode == BeginningOfGame) {
8136             /* Extra move from before last new; ignore */
8137             if (appData.debugMode) {
8138                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8139             }
8140           } else {
8141             if (appData.debugMode) {
8142                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8143                         cps->which, gameMode);
8144             }
8145
8146             SendToProgram("undo\n", cps);
8147           }
8148           return;
8149         }
8150
8151         switch (gameMode) {
8152           case BeginningOfGame:
8153             /* Extra move from before last reset; ignore */
8154             if (appData.debugMode) {
8155                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8156             }
8157             return;
8158
8159           case EndOfGame:
8160           case IcsIdle:
8161           default:
8162             /* Extra move after we tried to stop.  The mode test is
8163                not a reliable way of detecting this problem, but it's
8164                the best we can do on engines that don't support ping.
8165             */
8166             if (appData.debugMode) {
8167                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8168                         cps->which, gameMode);
8169             }
8170             SendToProgram("undo\n", cps);
8171             return;
8172
8173           case MachinePlaysWhite:
8174           case IcsPlayingWhite:
8175             machineWhite = TRUE;
8176             break;
8177
8178           case MachinePlaysBlack:
8179           case IcsPlayingBlack:
8180             machineWhite = FALSE;
8181             break;
8182
8183           case TwoMachinesPlay:
8184             machineWhite = (cps->twoMachinesColor[0] == 'w');
8185             break;
8186         }
8187         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8188             if (appData.debugMode) {
8189                 fprintf(debugFP,
8190                         "Ignoring move out of turn by %s, gameMode %d"
8191                         ", forwardMost %d\n",
8192                         cps->which, gameMode, forwardMostMove);
8193             }
8194             return;
8195         }
8196
8197         if(cps->alphaRank) AlphaRank(machineMove, 4);
8198         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8199                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8200             /* Machine move could not be parsed; ignore it. */
8201           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8202                     machineMove, _(cps->which));
8203             DisplayError(buf1, 0);
8204             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8205                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8206             if (gameMode == TwoMachinesPlay) {
8207               GameEnds(machineWhite ? BlackWins : WhiteWins,
8208                        buf1, GE_XBOARD);
8209             }
8210             return;
8211         }
8212
8213         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8214         /* So we have to redo legality test with true e.p. status here,  */
8215         /* to make sure an illegal e.p. capture does not slip through,   */
8216         /* to cause a forfeit on a justified illegal-move complaint      */
8217         /* of the opponent.                                              */
8218         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8219            ChessMove moveType;
8220            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8221                              fromY, fromX, toY, toX, promoChar);
8222             if(moveType == IllegalMove) {
8223               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8224                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8225                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8226                            buf1, GE_XBOARD);
8227                 return;
8228            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8229            /* [HGM] Kludge to handle engines that send FRC-style castling
8230               when they shouldn't (like TSCP-Gothic) */
8231            switch(moveType) {
8232              case WhiteASideCastleFR:
8233              case BlackASideCastleFR:
8234                toX+=2;
8235                currentMoveString[2]++;
8236                break;
8237              case WhiteHSideCastleFR:
8238              case BlackHSideCastleFR:
8239                toX--;
8240                currentMoveString[2]--;
8241                break;
8242              default: ; // nothing to do, but suppresses warning of pedantic compilers
8243            }
8244         }
8245         hintRequested = FALSE;
8246         lastHint[0] = NULLCHAR;
8247         bookRequested = FALSE;
8248         /* Program may be pondering now */
8249         cps->maybeThinking = TRUE;
8250         if (cps->sendTime == 2) cps->sendTime = 1;
8251         if (cps->offeredDraw) cps->offeredDraw--;
8252
8253         /* [AS] Save move info*/
8254         pvInfoList[ forwardMostMove ].score = programStats.score;
8255         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8256         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8257
8258         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8259
8260         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8261         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8262             int count = 0;
8263
8264             while( count < adjudicateLossPlies ) {
8265                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8266
8267                 if( count & 1 ) {
8268                     score = -score; /* Flip score for winning side */
8269                 }
8270
8271                 if( score > adjudicateLossThreshold ) {
8272                     break;
8273                 }
8274
8275                 count++;
8276             }
8277
8278             if( count >= adjudicateLossPlies ) {
8279                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8280
8281                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8282                     "Xboard adjudication",
8283                     GE_XBOARD );
8284
8285                 return;
8286             }
8287         }
8288
8289         if(Adjudicate(cps)) {
8290             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8291             return; // [HGM] adjudicate: for all automatic game ends
8292         }
8293
8294 #if ZIPPY
8295         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8296             first.initDone) {
8297           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8298                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8299                 SendToICS("draw ");
8300                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8301           }
8302           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8303           ics_user_moved = 1;
8304           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8305                 char buf[3*MSG_SIZ];
8306
8307                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8308                         programStats.score / 100.,
8309                         programStats.depth,
8310                         programStats.time / 100.,
8311                         (unsigned int)programStats.nodes,
8312                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8313                         programStats.movelist);
8314                 SendToICS(buf);
8315 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8316           }
8317         }
8318 #endif
8319
8320         /* [AS] Clear stats for next move */
8321         ClearProgramStats();
8322         thinkOutput[0] = NULLCHAR;
8323         hiddenThinkOutputState = 0;
8324
8325         bookHit = NULL;
8326         if (gameMode == TwoMachinesPlay) {
8327             /* [HGM] relaying draw offers moved to after reception of move */
8328             /* and interpreting offer as claim if it brings draw condition */
8329             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8330                 SendToProgram("draw\n", cps->other);
8331             }
8332             if (cps->other->sendTime) {
8333                 SendTimeRemaining(cps->other,
8334                                   cps->other->twoMachinesColor[0] == 'w');
8335             }
8336             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8337             if (firstMove && !bookHit) {
8338                 firstMove = FALSE;
8339                 if (cps->other->useColors) {
8340                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8341                 }
8342                 SendToProgram("go\n", cps->other);
8343             }
8344             cps->other->maybeThinking = TRUE;
8345         }
8346
8347         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8348
8349         if (!pausing && appData.ringBellAfterMoves) {
8350             RingBell();
8351         }
8352
8353         /*
8354          * Reenable menu items that were disabled while
8355          * machine was thinking
8356          */
8357         if (gameMode != TwoMachinesPlay)
8358             SetUserThinkingEnables();
8359
8360         // [HGM] book: after book hit opponent has received move and is now in force mode
8361         // force the book reply into it, and then fake that it outputted this move by jumping
8362         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8363         if(bookHit) {
8364                 static char bookMove[MSG_SIZ]; // a bit generous?
8365
8366                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8367                 strcat(bookMove, bookHit);
8368                 message = bookMove;
8369                 cps = cps->other;
8370                 programStats.nodes = programStats.depth = programStats.time =
8371                 programStats.score = programStats.got_only_move = 0;
8372                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8373
8374                 if(cps->lastPing != cps->lastPong) {
8375                     savedMessage = message; // args for deferred call
8376                     savedState = cps;
8377                     ScheduleDelayedEvent(DeferredBookMove, 10);
8378                     return;
8379                 }
8380                 goto FakeBookMove;
8381         }
8382
8383         return;
8384     }
8385
8386     /* Set special modes for chess engines.  Later something general
8387      *  could be added here; for now there is just one kludge feature,
8388      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8389      *  when "xboard" is given as an interactive command.
8390      */
8391     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8392         cps->useSigint = FALSE;
8393         cps->useSigterm = FALSE;
8394     }
8395     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8396       ParseFeatures(message+8, cps);
8397       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8398     }
8399
8400     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8401                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8402       int dummy, s=6; char buf[MSG_SIZ];
8403       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8404       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8405       if(startedFromSetupPosition) return;
8406       ParseFEN(boards[0], &dummy, message+s);
8407       DrawPosition(TRUE, boards[0]);
8408       startedFromSetupPosition = TRUE;
8409       return;
8410     }
8411     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8412      * want this, I was asked to put it in, and obliged.
8413      */
8414     if (!strncmp(message, "setboard ", 9)) {
8415         Board initial_position;
8416
8417         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8418
8419         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8420             DisplayError(_("Bad FEN received from engine"), 0);
8421             return ;
8422         } else {
8423            Reset(TRUE, FALSE);
8424            CopyBoard(boards[0], initial_position);
8425            initialRulePlies = FENrulePlies;
8426            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8427            else gameMode = MachinePlaysBlack;
8428            DrawPosition(FALSE, boards[currentMove]);
8429         }
8430         return;
8431     }
8432
8433     /*
8434      * Look for communication commands
8435      */
8436     if (!strncmp(message, "telluser ", 9)) {
8437         if(message[9] == '\\' && message[10] == '\\')
8438             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8439         PlayTellSound();
8440         DisplayNote(message + 9);
8441         return;
8442     }
8443     if (!strncmp(message, "tellusererror ", 14)) {
8444         cps->userError = 1;
8445         if(message[14] == '\\' && message[15] == '\\')
8446             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8447         PlayTellSound();
8448         DisplayError(message + 14, 0);
8449         return;
8450     }
8451     if (!strncmp(message, "tellopponent ", 13)) {
8452       if (appData.icsActive) {
8453         if (loggedOn) {
8454           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8455           SendToICS(buf1);
8456         }
8457       } else {
8458         DisplayNote(message + 13);
8459       }
8460       return;
8461     }
8462     if (!strncmp(message, "tellothers ", 11)) {
8463       if (appData.icsActive) {
8464         if (loggedOn) {
8465           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8466           SendToICS(buf1);
8467         }
8468       }
8469       return;
8470     }
8471     if (!strncmp(message, "tellall ", 8)) {
8472       if (appData.icsActive) {
8473         if (loggedOn) {
8474           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8475           SendToICS(buf1);
8476         }
8477       } else {
8478         DisplayNote(message + 8);
8479       }
8480       return;
8481     }
8482     if (strncmp(message, "warning", 7) == 0) {
8483         /* Undocumented feature, use tellusererror in new code */
8484         DisplayError(message, 0);
8485         return;
8486     }
8487     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8488         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8489         strcat(realname, " query");
8490         AskQuestion(realname, buf2, buf1, cps->pr);
8491         return;
8492     }
8493     /* Commands from the engine directly to ICS.  We don't allow these to be
8494      *  sent until we are logged on. Crafty kibitzes have been known to
8495      *  interfere with the login process.
8496      */
8497     if (loggedOn) {
8498         if (!strncmp(message, "tellics ", 8)) {
8499             SendToICS(message + 8);
8500             SendToICS("\n");
8501             return;
8502         }
8503         if (!strncmp(message, "tellicsnoalias ", 15)) {
8504             SendToICS(ics_prefix);
8505             SendToICS(message + 15);
8506             SendToICS("\n");
8507             return;
8508         }
8509         /* The following are for backward compatibility only */
8510         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8511             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8512             SendToICS(ics_prefix);
8513             SendToICS(message);
8514             SendToICS("\n");
8515             return;
8516         }
8517     }
8518     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8519         return;
8520     }
8521     /*
8522      * If the move is illegal, cancel it and redraw the board.
8523      * Also deal with other error cases.  Matching is rather loose
8524      * here to accommodate engines written before the spec.
8525      */
8526     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8527         strncmp(message, "Error", 5) == 0) {
8528         if (StrStr(message, "name") ||
8529             StrStr(message, "rating") || StrStr(message, "?") ||
8530             StrStr(message, "result") || StrStr(message, "board") ||
8531             StrStr(message, "bk") || StrStr(message, "computer") ||
8532             StrStr(message, "variant") || StrStr(message, "hint") ||
8533             StrStr(message, "random") || StrStr(message, "depth") ||
8534             StrStr(message, "accepted")) {
8535             return;
8536         }
8537         if (StrStr(message, "protover")) {
8538           /* Program is responding to input, so it's apparently done
8539              initializing, and this error message indicates it is
8540              protocol version 1.  So we don't need to wait any longer
8541              for it to initialize and send feature commands. */
8542           FeatureDone(cps, 1);
8543           cps->protocolVersion = 1;
8544           return;
8545         }
8546         cps->maybeThinking = FALSE;
8547
8548         if (StrStr(message, "draw")) {
8549             /* Program doesn't have "draw" command */
8550             cps->sendDrawOffers = 0;
8551             return;
8552         }
8553         if (cps->sendTime != 1 &&
8554             (StrStr(message, "time") || StrStr(message, "otim"))) {
8555           /* Program apparently doesn't have "time" or "otim" command */
8556           cps->sendTime = 0;
8557           return;
8558         }
8559         if (StrStr(message, "analyze")) {
8560             cps->analysisSupport = FALSE;
8561             cps->analyzing = FALSE;
8562 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8563             EditGameEvent(); // [HGM] try to preserve loaded game
8564             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8565             DisplayError(buf2, 0);
8566             return;
8567         }
8568         if (StrStr(message, "(no matching move)st")) {
8569           /* Special kludge for GNU Chess 4 only */
8570           cps->stKludge = TRUE;
8571           SendTimeControl(cps, movesPerSession, timeControl,
8572                           timeIncrement, appData.searchDepth,
8573                           searchTime);
8574           return;
8575         }
8576         if (StrStr(message, "(no matching move)sd")) {
8577           /* Special kludge for GNU Chess 4 only */
8578           cps->sdKludge = TRUE;
8579           SendTimeControl(cps, movesPerSession, timeControl,
8580                           timeIncrement, appData.searchDepth,
8581                           searchTime);
8582           return;
8583         }
8584         if (!StrStr(message, "llegal")) {
8585             return;
8586         }
8587         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8588             gameMode == IcsIdle) return;
8589         if (forwardMostMove <= backwardMostMove) return;
8590         if (pausing) PauseEvent();
8591       if(appData.forceIllegal) {
8592             // [HGM] illegal: machine refused move; force position after move into it
8593           SendToProgram("force\n", cps);
8594           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8595                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8596                 // when black is to move, while there might be nothing on a2 or black
8597                 // might already have the move. So send the board as if white has the move.
8598                 // But first we must change the stm of the engine, as it refused the last move
8599                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8600                 if(WhiteOnMove(forwardMostMove)) {
8601                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8602                     SendBoard(cps, forwardMostMove); // kludgeless board
8603                 } else {
8604                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8605                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8606                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8607                 }
8608           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8609             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8610                  gameMode == TwoMachinesPlay)
8611               SendToProgram("go\n", cps);
8612             return;
8613       } else
8614         if (gameMode == PlayFromGameFile) {
8615             /* Stop reading this game file */
8616             gameMode = EditGame;
8617             ModeHighlight();
8618         }
8619         /* [HGM] illegal-move claim should forfeit game when Xboard */
8620         /* only passes fully legal moves                            */
8621         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8622             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8623                                 "False illegal-move claim", GE_XBOARD );
8624             return; // do not take back move we tested as valid
8625         }
8626         currentMove = forwardMostMove-1;
8627         DisplayMove(currentMove-1); /* before DisplayMoveError */
8628         SwitchClocks(forwardMostMove-1); // [HGM] race
8629         DisplayBothClocks();
8630         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8631                 parseList[currentMove], _(cps->which));
8632         DisplayMoveError(buf1);
8633         DrawPosition(FALSE, boards[currentMove]);
8634
8635         SetUserThinkingEnables();
8636         return;
8637     }
8638     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8639         /* Program has a broken "time" command that
8640            outputs a string not ending in newline.
8641            Don't use it. */
8642         cps->sendTime = 0;
8643     }
8644
8645     /*
8646      * If chess program startup fails, exit with an error message.
8647      * Attempts to recover here are futile. [HGM] Well, we try anyway
8648      */
8649     if ((StrStr(message, "unknown host") != NULL)
8650         || (StrStr(message, "No remote directory") != NULL)
8651         || (StrStr(message, "not found") != NULL)
8652         || (StrStr(message, "No such file") != NULL)
8653         || (StrStr(message, "can't alloc") != NULL)
8654         || (StrStr(message, "Permission denied") != NULL)) {
8655
8656         cps->maybeThinking = FALSE;
8657         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8658                 _(cps->which), cps->program, cps->host, message);
8659         RemoveInputSource(cps->isr);
8660         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8661             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8662             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8663         }
8664         return;
8665     }
8666
8667     /*
8668      * Look for hint output
8669      */
8670     if (sscanf(message, "Hint: %s", buf1) == 1) {
8671         if (cps == &first && hintRequested) {
8672             hintRequested = FALSE;
8673             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8674                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8675                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8676                                     PosFlags(forwardMostMove),
8677                                     fromY, fromX, toY, toX, promoChar, buf1);
8678                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8679                 DisplayInformation(buf2);
8680             } else {
8681                 /* Hint move could not be parsed!? */
8682               snprintf(buf2, sizeof(buf2),
8683                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8684                         buf1, _(cps->which));
8685                 DisplayError(buf2, 0);
8686             }
8687         } else {
8688           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8689         }
8690         return;
8691     }
8692
8693     /*
8694      * Ignore other messages if game is not in progress
8695      */
8696     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8697         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8698
8699     /*
8700      * look for win, lose, draw, or draw offer
8701      */
8702     if (strncmp(message, "1-0", 3) == 0) {
8703         char *p, *q, *r = "";
8704         p = strchr(message, '{');
8705         if (p) {
8706             q = strchr(p, '}');
8707             if (q) {
8708                 *q = NULLCHAR;
8709                 r = p + 1;
8710             }
8711         }
8712         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8713         return;
8714     } else if (strncmp(message, "0-1", 3) == 0) {
8715         char *p, *q, *r = "";
8716         p = strchr(message, '{');
8717         if (p) {
8718             q = strchr(p, '}');
8719             if (q) {
8720                 *q = NULLCHAR;
8721                 r = p + 1;
8722             }
8723         }
8724         /* Kludge for Arasan 4.1 bug */
8725         if (strcmp(r, "Black resigns") == 0) {
8726             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8727             return;
8728         }
8729         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8730         return;
8731     } else if (strncmp(message, "1/2", 3) == 0) {
8732         char *p, *q, *r = "";
8733         p = strchr(message, '{');
8734         if (p) {
8735             q = strchr(p, '}');
8736             if (q) {
8737                 *q = NULLCHAR;
8738                 r = p + 1;
8739             }
8740         }
8741
8742         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8743         return;
8744
8745     } else if (strncmp(message, "White resign", 12) == 0) {
8746         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8747         return;
8748     } else if (strncmp(message, "Black resign", 12) == 0) {
8749         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8750         return;
8751     } else if (strncmp(message, "White matches", 13) == 0 ||
8752                strncmp(message, "Black matches", 13) == 0   ) {
8753         /* [HGM] ignore GNUShogi noises */
8754         return;
8755     } else if (strncmp(message, "White", 5) == 0 &&
8756                message[5] != '(' &&
8757                StrStr(message, "Black") == NULL) {
8758         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8759         return;
8760     } else if (strncmp(message, "Black", 5) == 0 &&
8761                message[5] != '(') {
8762         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8763         return;
8764     } else if (strcmp(message, "resign") == 0 ||
8765                strcmp(message, "computer resigns") == 0) {
8766         switch (gameMode) {
8767           case MachinePlaysBlack:
8768           case IcsPlayingBlack:
8769             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8770             break;
8771           case MachinePlaysWhite:
8772           case IcsPlayingWhite:
8773             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8774             break;
8775           case TwoMachinesPlay:
8776             if (cps->twoMachinesColor[0] == 'w')
8777               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8778             else
8779               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8780             break;
8781           default:
8782             /* can't happen */
8783             break;
8784         }
8785         return;
8786     } else if (strncmp(message, "opponent mates", 14) == 0) {
8787         switch (gameMode) {
8788           case MachinePlaysBlack:
8789           case IcsPlayingBlack:
8790             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8791             break;
8792           case MachinePlaysWhite:
8793           case IcsPlayingWhite:
8794             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8795             break;
8796           case TwoMachinesPlay:
8797             if (cps->twoMachinesColor[0] == 'w')
8798               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8799             else
8800               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8801             break;
8802           default:
8803             /* can't happen */
8804             break;
8805         }
8806         return;
8807     } else if (strncmp(message, "computer mates", 14) == 0) {
8808         switch (gameMode) {
8809           case MachinePlaysBlack:
8810           case IcsPlayingBlack:
8811             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8812             break;
8813           case MachinePlaysWhite:
8814           case IcsPlayingWhite:
8815             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8816             break;
8817           case TwoMachinesPlay:
8818             if (cps->twoMachinesColor[0] == 'w')
8819               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8820             else
8821               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8822             break;
8823           default:
8824             /* can't happen */
8825             break;
8826         }
8827         return;
8828     } else if (strncmp(message, "checkmate", 9) == 0) {
8829         if (WhiteOnMove(forwardMostMove)) {
8830             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8831         } else {
8832             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8833         }
8834         return;
8835     } else if (strstr(message, "Draw") != NULL ||
8836                strstr(message, "game is a draw") != NULL) {
8837         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8838         return;
8839     } else if (strstr(message, "offer") != NULL &&
8840                strstr(message, "draw") != NULL) {
8841 #if ZIPPY
8842         if (appData.zippyPlay && first.initDone) {
8843             /* Relay offer to ICS */
8844             SendToICS(ics_prefix);
8845             SendToICS("draw\n");
8846         }
8847 #endif
8848         cps->offeredDraw = 2; /* valid until this engine moves twice */
8849         if (gameMode == TwoMachinesPlay) {
8850             if (cps->other->offeredDraw) {
8851                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8852             /* [HGM] in two-machine mode we delay relaying draw offer      */
8853             /* until after we also have move, to see if it is really claim */
8854             }
8855         } else if (gameMode == MachinePlaysWhite ||
8856                    gameMode == MachinePlaysBlack) {
8857           if (userOfferedDraw) {
8858             DisplayInformation(_("Machine accepts your draw offer"));
8859             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8860           } else {
8861             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8862           }
8863         }
8864     }
8865
8866
8867     /*
8868      * Look for thinking output
8869      */
8870     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8871           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8872                                 ) {
8873         int plylev, mvleft, mvtot, curscore, time;
8874         char mvname[MOVE_LEN];
8875         u64 nodes; // [DM]
8876         char plyext;
8877         int ignore = FALSE;
8878         int prefixHint = FALSE;
8879         mvname[0] = NULLCHAR;
8880
8881         switch (gameMode) {
8882           case MachinePlaysBlack:
8883           case IcsPlayingBlack:
8884             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8885             break;
8886           case MachinePlaysWhite:
8887           case IcsPlayingWhite:
8888             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8889             break;
8890           case AnalyzeMode:
8891           case AnalyzeFile:
8892             break;
8893           case IcsObserving: /* [DM] icsEngineAnalyze */
8894             if (!appData.icsEngineAnalyze) ignore = TRUE;
8895             break;
8896           case TwoMachinesPlay:
8897             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8898                 ignore = TRUE;
8899             }
8900             break;
8901           default:
8902             ignore = TRUE;
8903             break;
8904         }
8905
8906         if (!ignore) {
8907             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8908             buf1[0] = NULLCHAR;
8909             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8910                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8911
8912                 if (plyext != ' ' && plyext != '\t') {
8913                     time *= 100;
8914                 }
8915
8916                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8917                 if( cps->scoreIsAbsolute &&
8918                     ( gameMode == MachinePlaysBlack ||
8919                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8920                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8921                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8922                      !WhiteOnMove(currentMove)
8923                     ) )
8924                 {
8925                     curscore = -curscore;
8926                 }
8927
8928                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8929
8930                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8931                         char buf[MSG_SIZ];
8932                         FILE *f;
8933                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8934                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8935                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8936                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8937                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8938                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8939                                 fclose(f);
8940                         } else DisplayError(_("failed writing PV"), 0);
8941                 }
8942
8943                 tempStats.depth = plylev;
8944                 tempStats.nodes = nodes;
8945                 tempStats.time = time;
8946                 tempStats.score = curscore;
8947                 tempStats.got_only_move = 0;
8948
8949                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8950                         int ticklen;
8951
8952                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8953                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8954                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8955                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8956                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8957                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8958                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8959                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8960                 }
8961
8962                 /* Buffer overflow protection */
8963                 if (pv[0] != NULLCHAR) {
8964                     if (strlen(pv) >= sizeof(tempStats.movelist)
8965                         && appData.debugMode) {
8966                         fprintf(debugFP,
8967                                 "PV is too long; using the first %u bytes.\n",
8968                                 (unsigned) sizeof(tempStats.movelist) - 1);
8969                     }
8970
8971                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8972                 } else {
8973                     sprintf(tempStats.movelist, " no PV\n");
8974                 }
8975
8976                 if (tempStats.seen_stat) {
8977                     tempStats.ok_to_send = 1;
8978                 }
8979
8980                 if (strchr(tempStats.movelist, '(') != NULL) {
8981                     tempStats.line_is_book = 1;
8982                     tempStats.nr_moves = 0;
8983                     tempStats.moves_left = 0;
8984                 } else {
8985                     tempStats.line_is_book = 0;
8986                 }
8987
8988                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8989                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8990
8991                 SendProgramStatsToFrontend( cps, &tempStats );
8992
8993                 /*
8994                     [AS] Protect the thinkOutput buffer from overflow... this
8995                     is only useful if buf1 hasn't overflowed first!
8996                 */
8997                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8998                          plylev,
8999                          (gameMode == TwoMachinesPlay ?
9000                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9001                          ((double) curscore) / 100.0,
9002                          prefixHint ? lastHint : "",
9003                          prefixHint ? " " : "" );
9004
9005                 if( buf1[0] != NULLCHAR ) {
9006                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9007
9008                     if( strlen(pv) > max_len ) {
9009                         if( appData.debugMode) {
9010                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9011                         }
9012                         pv[max_len+1] = '\0';
9013                     }
9014
9015                     strcat( thinkOutput, pv);
9016                 }
9017
9018                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9019                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9020                     DisplayMove(currentMove - 1);
9021                 }
9022                 return;
9023
9024             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9025                 /* crafty (9.25+) says "(only move) <move>"
9026                  * if there is only 1 legal move
9027                  */
9028                 sscanf(p, "(only move) %s", buf1);
9029                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9030                 sprintf(programStats.movelist, "%s (only move)", buf1);
9031                 programStats.depth = 1;
9032                 programStats.nr_moves = 1;
9033                 programStats.moves_left = 1;
9034                 programStats.nodes = 1;
9035                 programStats.time = 1;
9036                 programStats.got_only_move = 1;
9037
9038                 /* Not really, but we also use this member to
9039                    mean "line isn't going to change" (Crafty
9040                    isn't searching, so stats won't change) */
9041                 programStats.line_is_book = 1;
9042
9043                 SendProgramStatsToFrontend( cps, &programStats );
9044
9045                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9046                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9047                     DisplayMove(currentMove - 1);
9048                 }
9049                 return;
9050             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9051                               &time, &nodes, &plylev, &mvleft,
9052                               &mvtot, mvname) >= 5) {
9053                 /* The stat01: line is from Crafty (9.29+) in response
9054                    to the "." command */
9055                 programStats.seen_stat = 1;
9056                 cps->maybeThinking = TRUE;
9057
9058                 if (programStats.got_only_move || !appData.periodicUpdates)
9059                   return;
9060
9061                 programStats.depth = plylev;
9062                 programStats.time = time;
9063                 programStats.nodes = nodes;
9064                 programStats.moves_left = mvleft;
9065                 programStats.nr_moves = mvtot;
9066                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9067                 programStats.ok_to_send = 1;
9068                 programStats.movelist[0] = '\0';
9069
9070                 SendProgramStatsToFrontend( cps, &programStats );
9071
9072                 return;
9073
9074             } else if (strncmp(message,"++",2) == 0) {
9075                 /* Crafty 9.29+ outputs this */
9076                 programStats.got_fail = 2;
9077                 return;
9078
9079             } else if (strncmp(message,"--",2) == 0) {
9080                 /* Crafty 9.29+ outputs this */
9081                 programStats.got_fail = 1;
9082                 return;
9083
9084             } else if (thinkOutput[0] != NULLCHAR &&
9085                        strncmp(message, "    ", 4) == 0) {
9086                 unsigned message_len;
9087
9088                 p = message;
9089                 while (*p && *p == ' ') p++;
9090
9091                 message_len = strlen( p );
9092
9093                 /* [AS] Avoid buffer overflow */
9094                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9095                     strcat(thinkOutput, " ");
9096                     strcat(thinkOutput, p);
9097                 }
9098
9099                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9100                     strcat(programStats.movelist, " ");
9101                     strcat(programStats.movelist, p);
9102                 }
9103
9104                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9105                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9106                     DisplayMove(currentMove - 1);
9107                 }
9108                 return;
9109             }
9110         }
9111         else {
9112             buf1[0] = NULLCHAR;
9113
9114             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9115                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9116             {
9117                 ChessProgramStats cpstats;
9118
9119                 if (plyext != ' ' && plyext != '\t') {
9120                     time *= 100;
9121                 }
9122
9123                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9124                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9125                     curscore = -curscore;
9126                 }
9127
9128                 cpstats.depth = plylev;
9129                 cpstats.nodes = nodes;
9130                 cpstats.time = time;
9131                 cpstats.score = curscore;
9132                 cpstats.got_only_move = 0;
9133                 cpstats.movelist[0] = '\0';
9134
9135                 if (buf1[0] != NULLCHAR) {
9136                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9137                 }
9138
9139                 cpstats.ok_to_send = 0;
9140                 cpstats.line_is_book = 0;
9141                 cpstats.nr_moves = 0;
9142                 cpstats.moves_left = 0;
9143
9144                 SendProgramStatsToFrontend( cps, &cpstats );
9145             }
9146         }
9147     }
9148 }
9149
9150
9151 /* Parse a game score from the character string "game", and
9152    record it as the history of the current game.  The game
9153    score is NOT assumed to start from the standard position.
9154    The display is not updated in any way.
9155    */
9156 void
9157 ParseGameHistory (char *game)
9158 {
9159     ChessMove moveType;
9160     int fromX, fromY, toX, toY, boardIndex;
9161     char promoChar;
9162     char *p, *q;
9163     char buf[MSG_SIZ];
9164
9165     if (appData.debugMode)
9166       fprintf(debugFP, "Parsing game history: %s\n", game);
9167
9168     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9169     gameInfo.site = StrSave(appData.icsHost);
9170     gameInfo.date = PGNDate();
9171     gameInfo.round = StrSave("-");
9172
9173     /* Parse out names of players */
9174     while (*game == ' ') game++;
9175     p = buf;
9176     while (*game != ' ') *p++ = *game++;
9177     *p = NULLCHAR;
9178     gameInfo.white = StrSave(buf);
9179     while (*game == ' ') game++;
9180     p = buf;
9181     while (*game != ' ' && *game != '\n') *p++ = *game++;
9182     *p = NULLCHAR;
9183     gameInfo.black = StrSave(buf);
9184
9185     /* Parse moves */
9186     boardIndex = blackPlaysFirst ? 1 : 0;
9187     yynewstr(game);
9188     for (;;) {
9189         yyboardindex = boardIndex;
9190         moveType = (ChessMove) Myylex();
9191         switch (moveType) {
9192           case IllegalMove:             /* maybe suicide chess, etc. */
9193   if (appData.debugMode) {
9194     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9195     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9196     setbuf(debugFP, NULL);
9197   }
9198           case WhitePromotion:
9199           case BlackPromotion:
9200           case WhiteNonPromotion:
9201           case BlackNonPromotion:
9202           case NormalMove:
9203           case WhiteCapturesEnPassant:
9204           case BlackCapturesEnPassant:
9205           case WhiteKingSideCastle:
9206           case WhiteQueenSideCastle:
9207           case BlackKingSideCastle:
9208           case BlackQueenSideCastle:
9209           case WhiteKingSideCastleWild:
9210           case WhiteQueenSideCastleWild:
9211           case BlackKingSideCastleWild:
9212           case BlackQueenSideCastleWild:
9213           /* PUSH Fabien */
9214           case WhiteHSideCastleFR:
9215           case WhiteASideCastleFR:
9216           case BlackHSideCastleFR:
9217           case BlackASideCastleFR:
9218           /* POP Fabien */
9219             fromX = currentMoveString[0] - AAA;
9220             fromY = currentMoveString[1] - ONE;
9221             toX = currentMoveString[2] - AAA;
9222             toY = currentMoveString[3] - ONE;
9223             promoChar = currentMoveString[4];
9224             break;
9225           case WhiteDrop:
9226           case BlackDrop:
9227             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9228             fromX = moveType == WhiteDrop ?
9229               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9230             (int) CharToPiece(ToLower(currentMoveString[0]));
9231             fromY = DROP_RANK;
9232             toX = currentMoveString[2] - AAA;
9233             toY = currentMoveString[3] - ONE;
9234             promoChar = NULLCHAR;
9235             break;
9236           case AmbiguousMove:
9237             /* bug? */
9238             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9239   if (appData.debugMode) {
9240     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9241     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9242     setbuf(debugFP, NULL);
9243   }
9244             DisplayError(buf, 0);
9245             return;
9246           case ImpossibleMove:
9247             /* bug? */
9248             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9249   if (appData.debugMode) {
9250     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9251     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9252     setbuf(debugFP, NULL);
9253   }
9254             DisplayError(buf, 0);
9255             return;
9256           case EndOfFile:
9257             if (boardIndex < backwardMostMove) {
9258                 /* Oops, gap.  How did that happen? */
9259                 DisplayError(_("Gap in move list"), 0);
9260                 return;
9261             }
9262             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9263             if (boardIndex > forwardMostMove) {
9264                 forwardMostMove = boardIndex;
9265             }
9266             return;
9267           case ElapsedTime:
9268             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9269                 strcat(parseList[boardIndex-1], " ");
9270                 strcat(parseList[boardIndex-1], yy_text);
9271             }
9272             continue;
9273           case Comment:
9274           case PGNTag:
9275           case NAG:
9276           default:
9277             /* ignore */
9278             continue;
9279           case WhiteWins:
9280           case BlackWins:
9281           case GameIsDrawn:
9282           case GameUnfinished:
9283             if (gameMode == IcsExamining) {
9284                 if (boardIndex < backwardMostMove) {
9285                     /* Oops, gap.  How did that happen? */
9286                     return;
9287                 }
9288                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9289                 return;
9290             }
9291             gameInfo.result = moveType;
9292             p = strchr(yy_text, '{');
9293             if (p == NULL) p = strchr(yy_text, '(');
9294             if (p == NULL) {
9295                 p = yy_text;
9296                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9297             } else {
9298                 q = strchr(p, *p == '{' ? '}' : ')');
9299                 if (q != NULL) *q = NULLCHAR;
9300                 p++;
9301             }
9302             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9303             gameInfo.resultDetails = StrSave(p);
9304             continue;
9305         }
9306         if (boardIndex >= forwardMostMove &&
9307             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9308             backwardMostMove = blackPlaysFirst ? 1 : 0;
9309             return;
9310         }
9311         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9312                                  fromY, fromX, toY, toX, promoChar,
9313                                  parseList[boardIndex]);
9314         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9315         /* currentMoveString is set as a side-effect of yylex */
9316         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9317         strcat(moveList[boardIndex], "\n");
9318         boardIndex++;
9319         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9320         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9321           case MT_NONE:
9322           case MT_STALEMATE:
9323           default:
9324             break;
9325           case MT_CHECK:
9326             if(gameInfo.variant != VariantShogi)
9327                 strcat(parseList[boardIndex - 1], "+");
9328             break;
9329           case MT_CHECKMATE:
9330           case MT_STAINMATE:
9331             strcat(parseList[boardIndex - 1], "#");
9332             break;
9333         }
9334     }
9335 }
9336
9337
9338 /* Apply a move to the given board  */
9339 void
9340 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9341 {
9342   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9343   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9344
9345     /* [HGM] compute & store e.p. status and castling rights for new position */
9346     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9347
9348       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9349       oldEP = (signed char)board[EP_STATUS];
9350       board[EP_STATUS] = EP_NONE;
9351
9352   if (fromY == DROP_RANK) {
9353         /* must be first */
9354         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9355             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9356             return;
9357         }
9358         piece = board[toY][toX] = (ChessSquare) fromX;
9359   } else {
9360       int i;
9361
9362       if( board[toY][toX] != EmptySquare )
9363            board[EP_STATUS] = EP_CAPTURE;
9364
9365       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9366            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9367                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9368       } else
9369       if( board[fromY][fromX] == WhitePawn ) {
9370            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9371                board[EP_STATUS] = EP_PAWN_MOVE;
9372            if( toY-fromY==2) {
9373                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9374                         gameInfo.variant != VariantBerolina || toX < fromX)
9375                       board[EP_STATUS] = toX | berolina;
9376                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9377                         gameInfo.variant != VariantBerolina || toX > fromX)
9378                       board[EP_STATUS] = toX;
9379            }
9380       } else
9381       if( board[fromY][fromX] == BlackPawn ) {
9382            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9383                board[EP_STATUS] = EP_PAWN_MOVE;
9384            if( toY-fromY== -2) {
9385                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9386                         gameInfo.variant != VariantBerolina || toX < fromX)
9387                       board[EP_STATUS] = toX | berolina;
9388                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9389                         gameInfo.variant != VariantBerolina || toX > fromX)
9390                       board[EP_STATUS] = toX;
9391            }
9392        }
9393
9394        for(i=0; i<nrCastlingRights; i++) {
9395            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9396               board[CASTLING][i] == toX   && castlingRank[i] == toY
9397              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9398        }
9399
9400        if(gameInfo.variant == VariantSChess) { // update virginity
9401            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9402            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9403            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9404            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9405        }
9406
9407      if (fromX == toX && fromY == toY) return;
9408
9409      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9410      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9411      if(gameInfo.variant == VariantKnightmate)
9412          king += (int) WhiteUnicorn - (int) WhiteKing;
9413
9414     /* Code added by Tord: */
9415     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9416     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9417         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9418       board[fromY][fromX] = EmptySquare;
9419       board[toY][toX] = EmptySquare;
9420       if((toX > fromX) != (piece == WhiteRook)) {
9421         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9422       } else {
9423         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9424       }
9425     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9426                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9427       board[fromY][fromX] = EmptySquare;
9428       board[toY][toX] = EmptySquare;
9429       if((toX > fromX) != (piece == BlackRook)) {
9430         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9431       } else {
9432         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9433       }
9434     /* End of code added by Tord */
9435
9436     } else if (board[fromY][fromX] == king
9437         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9438         && toY == fromY && toX > fromX+1) {
9439         board[fromY][fromX] = EmptySquare;
9440         board[toY][toX] = king;
9441         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9442         board[fromY][BOARD_RGHT-1] = EmptySquare;
9443     } else if (board[fromY][fromX] == king
9444         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9445                && toY == fromY && toX < fromX-1) {
9446         board[fromY][fromX] = EmptySquare;
9447         board[toY][toX] = king;
9448         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9449         board[fromY][BOARD_LEFT] = EmptySquare;
9450     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9451                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9452                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9453                ) {
9454         /* white pawn promotion */
9455         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9456         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse)
9457            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9458             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9459         board[fromY][fromX] = EmptySquare;
9460     } else if ((fromY >= BOARD_HEIGHT>>1)
9461                && (toX != fromX)
9462                && gameInfo.variant != VariantXiangqi
9463                && gameInfo.variant != VariantBerolina
9464                && (board[fromY][fromX] == WhitePawn)
9465                && (board[toY][toX] == EmptySquare)) {
9466         board[fromY][fromX] = EmptySquare;
9467         board[toY][toX] = WhitePawn;
9468         captured = board[toY - 1][toX];
9469         board[toY - 1][toX] = EmptySquare;
9470     } else if ((fromY == BOARD_HEIGHT-4)
9471                && (toX == fromX)
9472                && gameInfo.variant == VariantBerolina
9473                && (board[fromY][fromX] == WhitePawn)
9474                && (board[toY][toX] == EmptySquare)) {
9475         board[fromY][fromX] = EmptySquare;
9476         board[toY][toX] = WhitePawn;
9477         if(oldEP & EP_BEROLIN_A) {
9478                 captured = board[fromY][fromX-1];
9479                 board[fromY][fromX-1] = EmptySquare;
9480         }else{  captured = board[fromY][fromX+1];
9481                 board[fromY][fromX+1] = EmptySquare;
9482         }
9483     } else if (board[fromY][fromX] == king
9484         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9485                && toY == fromY && toX > fromX+1) {
9486         board[fromY][fromX] = EmptySquare;
9487         board[toY][toX] = king;
9488         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9489         board[fromY][BOARD_RGHT-1] = EmptySquare;
9490     } else if (board[fromY][fromX] == king
9491         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9492                && toY == fromY && toX < fromX-1) {
9493         board[fromY][fromX] = EmptySquare;
9494         board[toY][toX] = king;
9495         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9496         board[fromY][BOARD_LEFT] = EmptySquare;
9497     } else if (fromY == 7 && fromX == 3
9498                && board[fromY][fromX] == BlackKing
9499                && toY == 7 && toX == 5) {
9500         board[fromY][fromX] = EmptySquare;
9501         board[toY][toX] = BlackKing;
9502         board[fromY][7] = EmptySquare;
9503         board[toY][4] = BlackRook;
9504     } else if (fromY == 7 && fromX == 3
9505                && board[fromY][fromX] == BlackKing
9506                && toY == 7 && toX == 1) {
9507         board[fromY][fromX] = EmptySquare;
9508         board[toY][toX] = BlackKing;
9509         board[fromY][0] = EmptySquare;
9510         board[toY][2] = BlackRook;
9511     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9512                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9513                && toY < promoRank && promoChar
9514                ) {
9515         /* black pawn promotion */
9516         board[toY][toX] = CharToPiece(ToLower(promoChar));
9517         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse)
9518            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9519             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9520         board[fromY][fromX] = EmptySquare;
9521     } else if ((fromY < BOARD_HEIGHT>>1)
9522                && (toX != fromX)
9523                && gameInfo.variant != VariantXiangqi
9524                && gameInfo.variant != VariantBerolina
9525                && (board[fromY][fromX] == BlackPawn)
9526                && (board[toY][toX] == EmptySquare)) {
9527         board[fromY][fromX] = EmptySquare;
9528         board[toY][toX] = BlackPawn;
9529         captured = board[toY + 1][toX];
9530         board[toY + 1][toX] = EmptySquare;
9531     } else if ((fromY == 3)
9532                && (toX == fromX)
9533                && gameInfo.variant == VariantBerolina
9534                && (board[fromY][fromX] == BlackPawn)
9535                && (board[toY][toX] == EmptySquare)) {
9536         board[fromY][fromX] = EmptySquare;
9537         board[toY][toX] = BlackPawn;
9538         if(oldEP & EP_BEROLIN_A) {
9539                 captured = board[fromY][fromX-1];
9540                 board[fromY][fromX-1] = EmptySquare;
9541         }else{  captured = board[fromY][fromX+1];
9542                 board[fromY][fromX+1] = EmptySquare;
9543         }
9544     } else {
9545         board[toY][toX] = board[fromY][fromX];
9546         board[fromY][fromX] = EmptySquare;
9547     }
9548   }
9549
9550     if (gameInfo.holdingsWidth != 0) {
9551
9552       /* !!A lot more code needs to be written to support holdings  */
9553       /* [HGM] OK, so I have written it. Holdings are stored in the */
9554       /* penultimate board files, so they are automaticlly stored   */
9555       /* in the game history.                                       */
9556       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9557                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9558         /* Delete from holdings, by decreasing count */
9559         /* and erasing image if necessary            */
9560         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9561         if(p < (int) BlackPawn) { /* white drop */
9562              p -= (int)WhitePawn;
9563                  p = PieceToNumber((ChessSquare)p);
9564              if(p >= gameInfo.holdingsSize) p = 0;
9565              if(--board[p][BOARD_WIDTH-2] <= 0)
9566                   board[p][BOARD_WIDTH-1] = EmptySquare;
9567              if((int)board[p][BOARD_WIDTH-2] < 0)
9568                         board[p][BOARD_WIDTH-2] = 0;
9569         } else {                  /* black drop */
9570              p -= (int)BlackPawn;
9571                  p = PieceToNumber((ChessSquare)p);
9572              if(p >= gameInfo.holdingsSize) p = 0;
9573              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9574                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9575              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9576                         board[BOARD_HEIGHT-1-p][1] = 0;
9577         }
9578       }
9579       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9580           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9581         /* [HGM] holdings: Add to holdings, if holdings exist */
9582         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9583                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9584                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9585         }
9586         p = (int) captured;
9587         if (p >= (int) BlackPawn) {
9588           p -= (int)BlackPawn;
9589           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9590                   /* in Shogi restore piece to its original  first */
9591                   captured = (ChessSquare) (DEMOTED captured);
9592                   p = DEMOTED p;
9593           }
9594           p = PieceToNumber((ChessSquare)p);
9595           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9596           board[p][BOARD_WIDTH-2]++;
9597           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9598         } else {
9599           p -= (int)WhitePawn;
9600           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9601                   captured = (ChessSquare) (DEMOTED captured);
9602                   p = DEMOTED p;
9603           }
9604           p = PieceToNumber((ChessSquare)p);
9605           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9606           board[BOARD_HEIGHT-1-p][1]++;
9607           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9608         }
9609       }
9610     } else if (gameInfo.variant == VariantAtomic) {
9611       if (captured != EmptySquare) {
9612         int y, x;
9613         for (y = toY-1; y <= toY+1; y++) {
9614           for (x = toX-1; x <= toX+1; x++) {
9615             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9616                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9617               board[y][x] = EmptySquare;
9618             }
9619           }
9620         }
9621         board[toY][toX] = EmptySquare;
9622       }
9623     }
9624     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9625         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9626     } else
9627     if(promoChar == '+') {
9628         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9629         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9630     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9631         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9632         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9633            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9634         board[toY][toX] = newPiece;
9635     }
9636     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9637                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9638         // [HGM] superchess: take promotion piece out of holdings
9639         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9640         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9641             if(!--board[k][BOARD_WIDTH-2])
9642                 board[k][BOARD_WIDTH-1] = EmptySquare;
9643         } else {
9644             if(!--board[BOARD_HEIGHT-1-k][1])
9645                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9646         }
9647     }
9648
9649 }
9650
9651 /* Updates forwardMostMove */
9652 void
9653 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9654 {
9655 //    forwardMostMove++; // [HGM] bare: moved downstream
9656
9657     (void) CoordsToAlgebraic(boards[forwardMostMove],
9658                              PosFlags(forwardMostMove),
9659                              fromY, fromX, toY, toX, promoChar,
9660                              parseList[forwardMostMove]);
9661
9662     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9663         int timeLeft; static int lastLoadFlag=0; int king, piece;
9664         piece = boards[forwardMostMove][fromY][fromX];
9665         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9666         if(gameInfo.variant == VariantKnightmate)
9667             king += (int) WhiteUnicorn - (int) WhiteKing;
9668         if(forwardMostMove == 0) {
9669             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9670                 fprintf(serverMoves, "%s;", UserName());
9671             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9672                 fprintf(serverMoves, "%s;", second.tidy);
9673             fprintf(serverMoves, "%s;", first.tidy);
9674             if(gameMode == MachinePlaysWhite)
9675                 fprintf(serverMoves, "%s;", UserName());
9676             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9677                 fprintf(serverMoves, "%s;", second.tidy);
9678         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9679         lastLoadFlag = loadFlag;
9680         // print base move
9681         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9682         // print castling suffix
9683         if( toY == fromY && piece == king ) {
9684             if(toX-fromX > 1)
9685                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9686             if(fromX-toX >1)
9687                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9688         }
9689         // e.p. suffix
9690         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9691              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9692              boards[forwardMostMove][toY][toX] == EmptySquare
9693              && fromX != toX && fromY != toY)
9694                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9695         // promotion suffix
9696         if(promoChar != NULLCHAR) {
9697             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9698                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9699                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9700             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9701         }
9702         if(!loadFlag) {
9703                 char buf[MOVE_LEN*2], *p; int len;
9704             fprintf(serverMoves, "/%d/%d",
9705                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9706             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9707             else                      timeLeft = blackTimeRemaining/1000;
9708             fprintf(serverMoves, "/%d", timeLeft);
9709                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9710                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9711                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9712                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9713             fprintf(serverMoves, "/%s", buf);
9714         }
9715         fflush(serverMoves);
9716     }
9717
9718     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9719         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9720       return;
9721     }
9722     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9723     if (commentList[forwardMostMove+1] != NULL) {
9724         free(commentList[forwardMostMove+1]);
9725         commentList[forwardMostMove+1] = NULL;
9726     }
9727     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9728     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9729     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9730     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9731     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9732     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9733     adjustedClock = FALSE;
9734     gameInfo.result = GameUnfinished;
9735     if (gameInfo.resultDetails != NULL) {
9736         free(gameInfo.resultDetails);
9737         gameInfo.resultDetails = NULL;
9738     }
9739     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9740                               moveList[forwardMostMove - 1]);
9741     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9742       case MT_NONE:
9743       case MT_STALEMATE:
9744       default:
9745         break;
9746       case MT_CHECK:
9747         if(gameInfo.variant != VariantShogi)
9748             strcat(parseList[forwardMostMove - 1], "+");
9749         break;
9750       case MT_CHECKMATE:
9751       case MT_STAINMATE:
9752         strcat(parseList[forwardMostMove - 1], "#");
9753         break;
9754     }
9755
9756 }
9757
9758 /* Updates currentMove if not pausing */
9759 void
9760 ShowMove (int fromX, int fromY, int toX, int toY)
9761 {
9762     int instant = (gameMode == PlayFromGameFile) ?
9763         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9764     if(appData.noGUI) return;
9765     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9766         if (!instant) {
9767             if (forwardMostMove == currentMove + 1) {
9768                 AnimateMove(boards[forwardMostMove - 1],
9769                             fromX, fromY, toX, toY);
9770             }
9771         }
9772         currentMove = forwardMostMove;
9773     }
9774
9775     if (instant) return;
9776
9777     DisplayMove(currentMove - 1);
9778     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9779             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9780                 SetHighlights(fromX, fromY, toX, toY);
9781             }
9782     }
9783     DrawPosition(FALSE, boards[currentMove]);
9784     DisplayBothClocks();
9785     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9786 }
9787
9788 void
9789 SendEgtPath (ChessProgramState *cps)
9790 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9791         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9792
9793         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9794
9795         while(*p) {
9796             char c, *q = name+1, *r, *s;
9797
9798             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9799             while(*p && *p != ',') *q++ = *p++;
9800             *q++ = ':'; *q = 0;
9801             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9802                 strcmp(name, ",nalimov:") == 0 ) {
9803                 // take nalimov path from the menu-changeable option first, if it is defined
9804               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9805                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9806             } else
9807             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9808                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9809                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9810                 s = r = StrStr(s, ":") + 1; // beginning of path info
9811                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9812                 c = *r; *r = 0;             // temporarily null-terminate path info
9813                     *--q = 0;               // strip of trailig ':' from name
9814                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9815                 *r = c;
9816                 SendToProgram(buf,cps);     // send egtbpath command for this format
9817             }
9818             if(*p == ',') p++; // read away comma to position for next format name
9819         }
9820 }
9821
9822 void
9823 InitChessProgram (ChessProgramState *cps, int setup)
9824 /* setup needed to setup FRC opening position */
9825 {
9826     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9827     if (appData.noChessProgram) return;
9828     hintRequested = FALSE;
9829     bookRequested = FALSE;
9830
9831     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9832     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9833     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9834     if(cps->memSize) { /* [HGM] memory */
9835       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9836         SendToProgram(buf, cps);
9837     }
9838     SendEgtPath(cps); /* [HGM] EGT */
9839     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9840       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9841         SendToProgram(buf, cps);
9842     }
9843
9844     SendToProgram(cps->initString, cps);
9845     if (gameInfo.variant != VariantNormal &&
9846         gameInfo.variant != VariantLoadable
9847         /* [HGM] also send variant if board size non-standard */
9848         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9849                                             ) {
9850       char *v = VariantName(gameInfo.variant);
9851       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9852         /* [HGM] in protocol 1 we have to assume all variants valid */
9853         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9854         DisplayFatalError(buf, 0, 1);
9855         return;
9856       }
9857
9858       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9859       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9860       if( gameInfo.variant == VariantXiangqi )
9861            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9862       if( gameInfo.variant == VariantShogi )
9863            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9864       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9865            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9866       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9867           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9868            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9869       if( gameInfo.variant == VariantCourier )
9870            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9871       if( gameInfo.variant == VariantSuper )
9872            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9873       if( gameInfo.variant == VariantGreat )
9874            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9875       if( gameInfo.variant == VariantSChess )
9876            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9877       if( gameInfo.variant == VariantGrand )
9878            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9879
9880       if(overruled) {
9881         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9882                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9883            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9884            if(StrStr(cps->variants, b) == NULL) {
9885                // specific sized variant not known, check if general sizing allowed
9886                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9887                    if(StrStr(cps->variants, "boardsize") == NULL) {
9888                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9889                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9890                        DisplayFatalError(buf, 0, 1);
9891                        return;
9892                    }
9893                    /* [HGM] here we really should compare with the maximum supported board size */
9894                }
9895            }
9896       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9897       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9898       SendToProgram(buf, cps);
9899     }
9900     currentlyInitializedVariant = gameInfo.variant;
9901
9902     /* [HGM] send opening position in FRC to first engine */
9903     if(setup) {
9904           SendToProgram("force\n", cps);
9905           SendBoard(cps, 0);
9906           /* engine is now in force mode! Set flag to wake it up after first move. */
9907           setboardSpoiledMachineBlack = 1;
9908     }
9909
9910     if (cps->sendICS) {
9911       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9912       SendToProgram(buf, cps);
9913     }
9914     cps->maybeThinking = FALSE;
9915     cps->offeredDraw = 0;
9916     if (!appData.icsActive) {
9917         SendTimeControl(cps, movesPerSession, timeControl,
9918                         timeIncrement, appData.searchDepth,
9919                         searchTime);
9920     }
9921     if (appData.showThinking
9922         // [HGM] thinking: four options require thinking output to be sent
9923         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9924                                 ) {
9925         SendToProgram("post\n", cps);
9926     }
9927     SendToProgram("hard\n", cps);
9928     if (!appData.ponderNextMove) {
9929         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9930            it without being sure what state we are in first.  "hard"
9931            is not a toggle, so that one is OK.
9932          */
9933         SendToProgram("easy\n", cps);
9934     }
9935     if (cps->usePing) {
9936       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9937       SendToProgram(buf, cps);
9938     }
9939     cps->initDone = TRUE;
9940     ClearEngineOutputPane(cps == &second);
9941 }
9942
9943
9944 void
9945 ResendOptions (ChessProgramState *cps)
9946 { // send the stored value of the options
9947   int i;
9948   char buf[MSG_SIZ];
9949   Option *opt = cps->option;
9950   for(i=0; i<cps->nrOptions; i++, opt++) {
9951       switch(opt->type) {
9952         case Spin:
9953         case Slider:
9954         case CheckBox:
9955             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9956           break;
9957         case ComboBox:
9958           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9959           break;
9960         default:
9961             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9962           break;
9963         case Button:
9964         case SaveButton:
9965           continue;
9966       }
9967       SendToProgram(buf, cps);
9968   }
9969 }
9970
9971 void
9972 StartChessProgram (ChessProgramState *cps)
9973 {
9974     char buf[MSG_SIZ];
9975     int err;
9976
9977     if (appData.noChessProgram) return;
9978     cps->initDone = FALSE;
9979
9980     if (strcmp(cps->host, "localhost") == 0) {
9981         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9982     } else if (*appData.remoteShell == NULLCHAR) {
9983         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9984     } else {
9985         if (*appData.remoteUser == NULLCHAR) {
9986           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9987                     cps->program);
9988         } else {
9989           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9990                     cps->host, appData.remoteUser, cps->program);
9991         }
9992         err = StartChildProcess(buf, "", &cps->pr);
9993     }
9994
9995     if (err != 0) {
9996       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9997         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9998         if(cps != &first) return;
9999         appData.noChessProgram = TRUE;
10000         ThawUI();
10001         SetNCPMode();
10002 //      DisplayFatalError(buf, err, 1);
10003 //      cps->pr = NoProc;
10004 //      cps->isr = NULL;
10005         return;
10006     }
10007
10008     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10009     if (cps->protocolVersion > 1) {
10010       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10011       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10012         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10013         cps->comboCnt = 0;  //                and values of combo boxes
10014       }
10015       SendToProgram(buf, cps);
10016       if(cps->reload) ResendOptions(cps);
10017     } else {
10018       SendToProgram("xboard\n", cps);
10019     }
10020 }
10021
10022 void
10023 TwoMachinesEventIfReady P((void))
10024 {
10025   static int curMess = 0;
10026   if (first.lastPing != first.lastPong) {
10027     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10028     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10029     return;
10030   }
10031   if (second.lastPing != second.lastPong) {
10032     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10033     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10034     return;
10035   }
10036   DisplayMessage("", ""); curMess = 0;
10037   ThawUI();
10038   TwoMachinesEvent();
10039 }
10040
10041 char *
10042 MakeName (char *template)
10043 {
10044     time_t clock;
10045     struct tm *tm;
10046     static char buf[MSG_SIZ];
10047     char *p = buf;
10048     int i;
10049
10050     clock = time((time_t *)NULL);
10051     tm = localtime(&clock);
10052
10053     while(*p++ = *template++) if(p[-1] == '%') {
10054         switch(*template++) {
10055           case 0:   *p = 0; return buf;
10056           case 'Y': i = tm->tm_year+1900; break;
10057           case 'y': i = tm->tm_year-100; break;
10058           case 'M': i = tm->tm_mon+1; break;
10059           case 'd': i = tm->tm_mday; break;
10060           case 'h': i = tm->tm_hour; break;
10061           case 'm': i = tm->tm_min; break;
10062           case 's': i = tm->tm_sec; break;
10063           default:  i = 0;
10064         }
10065         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10066     }
10067     return buf;
10068 }
10069
10070 int
10071 CountPlayers (char *p)
10072 {
10073     int n = 0;
10074     while(p = strchr(p, '\n')) p++, n++; // count participants
10075     return n;
10076 }
10077
10078 FILE *
10079 WriteTourneyFile (char *results, FILE *f)
10080 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10081     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10082     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10083         // create a file with tournament description
10084         fprintf(f, "-participants {%s}\n", appData.participants);
10085         fprintf(f, "-seedBase %d\n", appData.seedBase);
10086         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10087         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10088         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10089         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10090         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10091         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10092         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10093         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10094         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10095         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10096         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10097         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10098         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10099         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10100         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10101         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10102         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10103         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10104         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10105         fprintf(f, "-smpCores %d\n", appData.smpCores);
10106         if(searchTime > 0)
10107                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10108         else {
10109                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10110                 fprintf(f, "-tc %s\n", appData.timeControl);
10111                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10112         }
10113         fprintf(f, "-results \"%s\"\n", results);
10114     }
10115     return f;
10116 }
10117
10118 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10119
10120 void
10121 Substitute (char *participants, int expunge)
10122 {
10123     int i, changed, changes=0, nPlayers=0;
10124     char *p, *q, *r, buf[MSG_SIZ];
10125     if(participants == NULL) return;
10126     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10127     r = p = participants; q = appData.participants;
10128     while(*p && *p == *q) {
10129         if(*p == '\n') r = p+1, nPlayers++;
10130         p++; q++;
10131     }
10132     if(*p) { // difference
10133         while(*p && *p++ != '\n');
10134         while(*q && *q++ != '\n');
10135       changed = nPlayers;
10136         changes = 1 + (strcmp(p, q) != 0);
10137     }
10138     if(changes == 1) { // a single engine mnemonic was changed
10139         q = r; while(*q) nPlayers += (*q++ == '\n');
10140         p = buf; while(*r && (*p = *r++) != '\n') p++;
10141         *p = NULLCHAR;
10142         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10143         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10144         if(mnemonic[i]) { // The substitute is valid
10145             FILE *f;
10146             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10147                 flock(fileno(f), LOCK_EX);
10148                 ParseArgsFromFile(f);
10149                 fseek(f, 0, SEEK_SET);
10150                 FREE(appData.participants); appData.participants = participants;
10151                 if(expunge) { // erase results of replaced engine
10152                     int len = strlen(appData.results), w, b, dummy;
10153                     for(i=0; i<len; i++) {
10154                         Pairing(i, nPlayers, &w, &b, &dummy);
10155                         if((w == changed || b == changed) && appData.results[i] == '*') {
10156                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10157                             fclose(f);
10158                             return;
10159                         }
10160                     }
10161                     for(i=0; i<len; i++) {
10162                         Pairing(i, nPlayers, &w, &b, &dummy);
10163                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10164                     }
10165                 }
10166                 WriteTourneyFile(appData.results, f);
10167                 fclose(f); // release lock
10168                 return;
10169             }
10170         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10171     }
10172     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10173     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10174     free(participants);
10175     return;
10176 }
10177
10178 int
10179 CheckPlayers (char *participants)
10180 {
10181         int i;
10182         char buf[MSG_SIZ], *p;
10183         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10184         while(p = strchr(participants, '\n')) {
10185             *p = NULLCHAR;
10186             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10187             if(!mnemonic[i]) {
10188                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10189                 *p = '\n';
10190                 DisplayError(buf, 0);
10191                 return 1;
10192             }
10193             *p = '\n';
10194             participants = p + 1;
10195         }
10196         return 0;
10197 }
10198
10199 int
10200 CreateTourney (char *name)
10201 {
10202         FILE *f;
10203         if(matchMode && strcmp(name, appData.tourneyFile)) {
10204              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10205         }
10206         if(name[0] == NULLCHAR) {
10207             if(appData.participants[0])
10208                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10209             return 0;
10210         }
10211         f = fopen(name, "r");
10212         if(f) { // file exists
10213             ASSIGN(appData.tourneyFile, name);
10214             ParseArgsFromFile(f); // parse it
10215         } else {
10216             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10217             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10218                 DisplayError(_("Not enough participants"), 0);
10219                 return 0;
10220             }
10221             if(CheckPlayers(appData.participants)) return 0;
10222             ASSIGN(appData.tourneyFile, name);
10223             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10224             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10225         }
10226         fclose(f);
10227         appData.noChessProgram = FALSE;
10228         appData.clockMode = TRUE;
10229         SetGNUMode();
10230         return 1;
10231 }
10232
10233 int
10234 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10235 {
10236     char buf[MSG_SIZ], *p, *q;
10237     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10238     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10239     skip = !all && group[0]; // if group requested, we start in skip mode
10240     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10241         p = names; q = buf; header = 0;
10242         while(*p && *p != '\n') *q++ = *p++;
10243         *q = 0;
10244         if(*p == '\n') p++;
10245         if(buf[0] == '#') {
10246             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10247             depth++; // we must be entering a new group
10248             if(all) continue; // suppress printing group headers when complete list requested
10249             header = 1;
10250             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10251         }
10252         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10253         if(engineList[i]) free(engineList[i]);
10254         engineList[i] = strdup(buf);
10255         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10256         if(engineMnemonic[i]) free(engineMnemonic[i]);
10257         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10258             strcat(buf, " (");
10259             sscanf(q + 8, "%s", buf + strlen(buf));
10260             strcat(buf, ")");
10261         }
10262         engineMnemonic[i] = strdup(buf);
10263         i++;
10264     }
10265     engineList[i] = engineMnemonic[i] = NULL;
10266     return i;
10267 }
10268
10269 // following implemented as macro to avoid type limitations
10270 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10271
10272 void
10273 SwapEngines (int n)
10274 {   // swap settings for first engine and other engine (so far only some selected options)
10275     int h;
10276     char *p;
10277     if(n == 0) return;
10278     SWAP(directory, p)
10279     SWAP(chessProgram, p)
10280     SWAP(isUCI, h)
10281     SWAP(hasOwnBookUCI, h)
10282     SWAP(protocolVersion, h)
10283     SWAP(reuse, h)
10284     SWAP(scoreIsAbsolute, h)
10285     SWAP(timeOdds, h)
10286     SWAP(logo, p)
10287     SWAP(pgnName, p)
10288     SWAP(pvSAN, h)
10289     SWAP(engOptions, p)
10290     SWAP(engInitString, p)
10291     SWAP(computerString, p)
10292     SWAP(features, p)
10293     SWAP(fenOverride, p)
10294     SWAP(NPS, h)
10295     SWAP(accumulateTC, h)
10296     SWAP(host, p)
10297 }
10298
10299 int
10300 GetEngineLine (char *s, int n)
10301 {
10302     int i;
10303     char buf[MSG_SIZ];
10304     extern char *icsNames;
10305     if(!s || !*s) return 0;
10306     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10307     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10308     if(!mnemonic[i]) return 0;
10309     if(n == 11) return 1; // just testing if there was a match
10310     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10311     if(n == 1) SwapEngines(n);
10312     ParseArgsFromString(buf);
10313     if(n == 1) SwapEngines(n);
10314     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10315         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10316         ParseArgsFromString(buf);
10317     }
10318     return 1;
10319 }
10320
10321 int
10322 SetPlayer (int player, char *p)
10323 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10324     int i;
10325     char buf[MSG_SIZ], *engineName;
10326     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10327     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10328     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10329     if(mnemonic[i]) {
10330         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10331         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10332         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10333         ParseArgsFromString(buf);
10334     }
10335     free(engineName);
10336     return i;
10337 }
10338
10339 char *recentEngines;
10340
10341 void
10342 RecentEngineEvent (int nr)
10343 {
10344     int n;
10345 //    SwapEngines(1); // bump first to second
10346 //    ReplaceEngine(&second, 1); // and load it there
10347     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10348     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10349     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10350         ReplaceEngine(&first, 0);
10351         FloatToFront(&appData.recentEngineList, command[n]);
10352     }
10353 }
10354
10355 int
10356 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10357 {   // determine players from game number
10358     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10359
10360     if(appData.tourneyType == 0) {
10361         roundsPerCycle = (nPlayers - 1) | 1;
10362         pairingsPerRound = nPlayers / 2;
10363     } else if(appData.tourneyType > 0) {
10364         roundsPerCycle = nPlayers - appData.tourneyType;
10365         pairingsPerRound = appData.tourneyType;
10366     }
10367     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10368     gamesPerCycle = gamesPerRound * roundsPerCycle;
10369     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10370     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10371     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10372     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10373     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10374     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10375
10376     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10377     if(appData.roundSync) *syncInterval = gamesPerRound;
10378
10379     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10380
10381     if(appData.tourneyType == 0) {
10382         if(curPairing == (nPlayers-1)/2 ) {
10383             *whitePlayer = curRound;
10384             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10385         } else {
10386             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10387             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10388             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10389             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10390         }
10391     } else if(appData.tourneyType > 1) {
10392         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10393         *whitePlayer = curRound + appData.tourneyType;
10394     } else if(appData.tourneyType > 0) {
10395         *whitePlayer = curPairing;
10396         *blackPlayer = curRound + appData.tourneyType;
10397     }
10398
10399     // take care of white/black alternation per round.
10400     // For cycles and games this is already taken care of by default, derived from matchGame!
10401     return curRound & 1;
10402 }
10403
10404 int
10405 NextTourneyGame (int nr, int *swapColors)
10406 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10407     char *p, *q;
10408     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10409     FILE *tf;
10410     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10411     tf = fopen(appData.tourneyFile, "r");
10412     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10413     ParseArgsFromFile(tf); fclose(tf);
10414     InitTimeControls(); // TC might be altered from tourney file
10415
10416     nPlayers = CountPlayers(appData.participants); // count participants
10417     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10418     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10419
10420     if(syncInterval) {
10421         p = q = appData.results;
10422         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10423         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10424             DisplayMessage(_("Waiting for other game(s)"),"");
10425             waitingForGame = TRUE;
10426             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10427             return 0;
10428         }
10429         waitingForGame = FALSE;
10430     }
10431
10432     if(appData.tourneyType < 0) {
10433         if(nr>=0 && !pairingReceived) {
10434             char buf[1<<16];
10435             if(pairing.pr == NoProc) {
10436                 if(!appData.pairingEngine[0]) {
10437                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10438                     return 0;
10439                 }
10440                 StartChessProgram(&pairing); // starts the pairing engine
10441             }
10442             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10443             SendToProgram(buf, &pairing);
10444             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10445             SendToProgram(buf, &pairing);
10446             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10447         }
10448         pairingReceived = 0;                              // ... so we continue here
10449         *swapColors = 0;
10450         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10451         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10452         matchGame = 1; roundNr = nr / syncInterval + 1;
10453     }
10454
10455     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10456
10457     // redefine engines, engine dir, etc.
10458     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10459     if(first.pr == NoProc) {
10460       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10461       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10462     }
10463     if(second.pr == NoProc) {
10464       SwapEngines(1);
10465       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10466       SwapEngines(1);         // and make that valid for second engine by swapping
10467       InitEngine(&second, 1);
10468     }
10469     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10470     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10471     return 1;
10472 }
10473
10474 void
10475 NextMatchGame ()
10476 {   // performs game initialization that does not invoke engines, and then tries to start the game
10477     int res, firstWhite, swapColors = 0;
10478     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10479     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
10480         char buf[MSG_SIZ];
10481         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10482         if(strcmp(buf, currentDebugFile)) { // name has changed
10483             FILE *f = fopen(buf, "w");
10484             if(f) { // if opening the new file failed, just keep using the old one
10485                 ASSIGN(currentDebugFile, buf);
10486                 fclose(debugFP);
10487                 debugFP = f;
10488             }
10489             if(appData.serverFileName) {
10490                 if(serverFP) fclose(serverFP);
10491                 serverFP = fopen(appData.serverFileName, "w");
10492                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10493                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10494             }
10495         }
10496     }
10497     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10498     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10499     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10500     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10501     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10502     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10503     Reset(FALSE, first.pr != NoProc);
10504     res = LoadGameOrPosition(matchGame); // setup game
10505     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10506     if(!res) return; // abort when bad game/pos file
10507     TwoMachinesEvent();
10508 }
10509
10510 void
10511 UserAdjudicationEvent (int result)
10512 {
10513     ChessMove gameResult = GameIsDrawn;
10514
10515     if( result > 0 ) {
10516         gameResult = WhiteWins;
10517     }
10518     else if( result < 0 ) {
10519         gameResult = BlackWins;
10520     }
10521
10522     if( gameMode == TwoMachinesPlay ) {
10523         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10524     }
10525 }
10526
10527
10528 // [HGM] save: calculate checksum of game to make games easily identifiable
10529 int
10530 StringCheckSum (char *s)
10531 {
10532         int i = 0;
10533         if(s==NULL) return 0;
10534         while(*s) i = i*259 + *s++;
10535         return i;
10536 }
10537
10538 int
10539 GameCheckSum ()
10540 {
10541         int i, sum=0;
10542         for(i=backwardMostMove; i<forwardMostMove; i++) {
10543                 sum += pvInfoList[i].depth;
10544                 sum += StringCheckSum(parseList[i]);
10545                 sum += StringCheckSum(commentList[i]);
10546                 sum *= 261;
10547         }
10548         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10549         return sum + StringCheckSum(commentList[i]);
10550 } // end of save patch
10551
10552 void
10553 GameEnds (ChessMove result, char *resultDetails, int whosays)
10554 {
10555     GameMode nextGameMode;
10556     int isIcsGame;
10557     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10558
10559     if(endingGame) return; /* [HGM] crash: forbid recursion */
10560     endingGame = 1;
10561     if(twoBoards) { // [HGM] dual: switch back to one board
10562         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10563         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10564     }
10565     if (appData.debugMode) {
10566       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10567               result, resultDetails ? resultDetails : "(null)", whosays);
10568     }
10569
10570     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10571
10572     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10573
10574     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10575         /* If we are playing on ICS, the server decides when the
10576            game is over, but the engine can offer to draw, claim
10577            a draw, or resign.
10578          */
10579 #if ZIPPY
10580         if (appData.zippyPlay && first.initDone) {
10581             if (result == GameIsDrawn) {
10582                 /* In case draw still needs to be claimed */
10583                 SendToICS(ics_prefix);
10584                 SendToICS("draw\n");
10585             } else if (StrCaseStr(resultDetails, "resign")) {
10586                 SendToICS(ics_prefix);
10587                 SendToICS("resign\n");
10588             }
10589         }
10590 #endif
10591         endingGame = 0; /* [HGM] crash */
10592         return;
10593     }
10594
10595     /* If we're loading the game from a file, stop */
10596     if (whosays == GE_FILE) {
10597       (void) StopLoadGameTimer();
10598       gameFileFP = NULL;
10599     }
10600
10601     /* Cancel draw offers */
10602     first.offeredDraw = second.offeredDraw = 0;
10603
10604     /* If this is an ICS game, only ICS can really say it's done;
10605        if not, anyone can. */
10606     isIcsGame = (gameMode == IcsPlayingWhite ||
10607                  gameMode == IcsPlayingBlack ||
10608                  gameMode == IcsObserving    ||
10609                  gameMode == IcsExamining);
10610
10611     if (!isIcsGame || whosays == GE_ICS) {
10612         /* OK -- not an ICS game, or ICS said it was done */
10613         StopClocks();
10614         if (!isIcsGame && !appData.noChessProgram)
10615           SetUserThinkingEnables();
10616
10617         /* [HGM] if a machine claims the game end we verify this claim */
10618         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10619             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10620                 char claimer;
10621                 ChessMove trueResult = (ChessMove) -1;
10622
10623                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10624                                             first.twoMachinesColor[0] :
10625                                             second.twoMachinesColor[0] ;
10626
10627                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10628                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10629                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10630                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10631                 } else
10632                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10633                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10634                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10635                 } else
10636                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10637                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10638                 }
10639
10640                 // now verify win claims, but not in drop games, as we don't understand those yet
10641                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10642                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10643                     (result == WhiteWins && claimer == 'w' ||
10644                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10645                       if (appData.debugMode) {
10646                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10647                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10648                       }
10649                       if(result != trueResult) {
10650                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10651                               result = claimer == 'w' ? BlackWins : WhiteWins;
10652                               resultDetails = buf;
10653                       }
10654                 } else
10655                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10656                     && (forwardMostMove <= backwardMostMove ||
10657                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10658                         (claimer=='b')==(forwardMostMove&1))
10659                                                                                   ) {
10660                       /* [HGM] verify: draws that were not flagged are false claims */
10661                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10662                       result = claimer == 'w' ? BlackWins : WhiteWins;
10663                       resultDetails = buf;
10664                 }
10665                 /* (Claiming a loss is accepted no questions asked!) */
10666             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10667                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10668                 result = GameUnfinished;
10669                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10670             }
10671             /* [HGM] bare: don't allow bare King to win */
10672             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10673                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10674                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10675                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10676                && result != GameIsDrawn)
10677             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10678                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10679                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10680                         if(p >= 0 && p <= (int)WhiteKing) k++;
10681                 }
10682                 if (appData.debugMode) {
10683                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10684                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10685                 }
10686                 if(k <= 1) {
10687                         result = GameIsDrawn;
10688                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10689                         resultDetails = buf;
10690                 }
10691             }
10692         }
10693
10694
10695         if(serverMoves != NULL && !loadFlag) { char c = '=';
10696             if(result==WhiteWins) c = '+';
10697             if(result==BlackWins) c = '-';
10698             if(resultDetails != NULL)
10699                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10700         }
10701         if (resultDetails != NULL) {
10702             gameInfo.result = result;
10703             gameInfo.resultDetails = StrSave(resultDetails);
10704
10705             /* display last move only if game was not loaded from file */
10706             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10707                 DisplayMove(currentMove - 1);
10708
10709             if (forwardMostMove != 0) {
10710                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10711                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10712                                                                 ) {
10713                     if (*appData.saveGameFile != NULLCHAR) {
10714                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10715                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10716                         else
10717                         SaveGameToFile(appData.saveGameFile, TRUE);
10718                     } else if (appData.autoSaveGames) {
10719                         AutoSaveGame();
10720                     }
10721                     if (*appData.savePositionFile != NULLCHAR) {
10722                         SavePositionToFile(appData.savePositionFile);
10723                     }
10724                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10725                 }
10726             }
10727
10728             /* Tell program how game ended in case it is learning */
10729             /* [HGM] Moved this to after saving the PGN, just in case */
10730             /* engine died and we got here through time loss. In that */
10731             /* case we will get a fatal error writing the pipe, which */
10732             /* would otherwise lose us the PGN.                       */
10733             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10734             /* output during GameEnds should never be fatal anymore   */
10735             if (gameMode == MachinePlaysWhite ||
10736                 gameMode == MachinePlaysBlack ||
10737                 gameMode == TwoMachinesPlay ||
10738                 gameMode == IcsPlayingWhite ||
10739                 gameMode == IcsPlayingBlack ||
10740                 gameMode == BeginningOfGame) {
10741                 char buf[MSG_SIZ];
10742                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10743                         resultDetails);
10744                 if (first.pr != NoProc) {
10745                     SendToProgram(buf, &first);
10746                 }
10747                 if (second.pr != NoProc &&
10748                     gameMode == TwoMachinesPlay) {
10749                     SendToProgram(buf, &second);
10750                 }
10751             }
10752         }
10753
10754         if (appData.icsActive) {
10755             if (appData.quietPlay &&
10756                 (gameMode == IcsPlayingWhite ||
10757                  gameMode == IcsPlayingBlack)) {
10758                 SendToICS(ics_prefix);
10759                 SendToICS("set shout 1\n");
10760             }
10761             nextGameMode = IcsIdle;
10762             ics_user_moved = FALSE;
10763             /* clean up premove.  It's ugly when the game has ended and the
10764              * premove highlights are still on the board.
10765              */
10766             if (gotPremove) {
10767               gotPremove = FALSE;
10768               ClearPremoveHighlights();
10769               DrawPosition(FALSE, boards[currentMove]);
10770             }
10771             if (whosays == GE_ICS) {
10772                 switch (result) {
10773                 case WhiteWins:
10774                     if (gameMode == IcsPlayingWhite)
10775                         PlayIcsWinSound();
10776                     else if(gameMode == IcsPlayingBlack)
10777                         PlayIcsLossSound();
10778                     break;
10779                 case BlackWins:
10780                     if (gameMode == IcsPlayingBlack)
10781                         PlayIcsWinSound();
10782                     else if(gameMode == IcsPlayingWhite)
10783                         PlayIcsLossSound();
10784                     break;
10785                 case GameIsDrawn:
10786                     PlayIcsDrawSound();
10787                     break;
10788                 default:
10789                     PlayIcsUnfinishedSound();
10790                 }
10791             }
10792         } else if (gameMode == EditGame ||
10793                    gameMode == PlayFromGameFile ||
10794                    gameMode == AnalyzeMode ||
10795                    gameMode == AnalyzeFile) {
10796             nextGameMode = gameMode;
10797         } else {
10798             nextGameMode = EndOfGame;
10799         }
10800         pausing = FALSE;
10801         ModeHighlight();
10802     } else {
10803         nextGameMode = gameMode;
10804     }
10805
10806     if (appData.noChessProgram) {
10807         gameMode = nextGameMode;
10808         ModeHighlight();
10809         endingGame = 0; /* [HGM] crash */
10810         return;
10811     }
10812
10813     if (first.reuse) {
10814         /* Put first chess program into idle state */
10815         if (first.pr != NoProc &&
10816             (gameMode == MachinePlaysWhite ||
10817              gameMode == MachinePlaysBlack ||
10818              gameMode == TwoMachinesPlay ||
10819              gameMode == IcsPlayingWhite ||
10820              gameMode == IcsPlayingBlack ||
10821              gameMode == BeginningOfGame)) {
10822             SendToProgram("force\n", &first);
10823             if (first.usePing) {
10824               char buf[MSG_SIZ];
10825               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10826               SendToProgram(buf, &first);
10827             }
10828         }
10829     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10830         /* Kill off first chess program */
10831         if (first.isr != NULL)
10832           RemoveInputSource(first.isr);
10833         first.isr = NULL;
10834
10835         if (first.pr != NoProc) {
10836             ExitAnalyzeMode();
10837             DoSleep( appData.delayBeforeQuit );
10838             SendToProgram("quit\n", &first);
10839             DoSleep( appData.delayAfterQuit );
10840             DestroyChildProcess(first.pr, first.useSigterm);
10841             first.reload = TRUE;
10842         }
10843         first.pr = NoProc;
10844     }
10845     if (second.reuse) {
10846         /* Put second chess program into idle state */
10847         if (second.pr != NoProc &&
10848             gameMode == TwoMachinesPlay) {
10849             SendToProgram("force\n", &second);
10850             if (second.usePing) {
10851               char buf[MSG_SIZ];
10852               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10853               SendToProgram(buf, &second);
10854             }
10855         }
10856     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10857         /* Kill off second chess program */
10858         if (second.isr != NULL)
10859           RemoveInputSource(second.isr);
10860         second.isr = NULL;
10861
10862         if (second.pr != NoProc) {
10863             DoSleep( appData.delayBeforeQuit );
10864             SendToProgram("quit\n", &second);
10865             DoSleep( appData.delayAfterQuit );
10866             DestroyChildProcess(second.pr, second.useSigterm);
10867             second.reload = TRUE;
10868         }
10869         second.pr = NoProc;
10870     }
10871
10872     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10873         char resChar = '=';
10874         switch (result) {
10875         case WhiteWins:
10876           resChar = '+';
10877           if (first.twoMachinesColor[0] == 'w') {
10878             first.matchWins++;
10879           } else {
10880             second.matchWins++;
10881           }
10882           break;
10883         case BlackWins:
10884           resChar = '-';
10885           if (first.twoMachinesColor[0] == 'b') {
10886             first.matchWins++;
10887           } else {
10888             second.matchWins++;
10889           }
10890           break;
10891         case GameUnfinished:
10892           resChar = ' ';
10893         default:
10894           break;
10895         }
10896
10897         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10898         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10899             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10900             ReserveGame(nextGame, resChar); // sets nextGame
10901             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10902             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10903         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10904
10905         if (nextGame <= appData.matchGames && !abortMatch) {
10906             gameMode = nextGameMode;
10907             matchGame = nextGame; // this will be overruled in tourney mode!
10908             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10909             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10910             endingGame = 0; /* [HGM] crash */
10911             return;
10912         } else {
10913             gameMode = nextGameMode;
10914             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10915                      first.tidy, second.tidy,
10916                      first.matchWins, second.matchWins,
10917                      appData.matchGames - (first.matchWins + second.matchWins));
10918             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10919             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10920             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10921             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10922                 first.twoMachinesColor = "black\n";
10923                 second.twoMachinesColor = "white\n";
10924             } else {
10925                 first.twoMachinesColor = "white\n";
10926                 second.twoMachinesColor = "black\n";
10927             }
10928         }
10929     }
10930     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10931         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10932       ExitAnalyzeMode();
10933     gameMode = nextGameMode;
10934     ModeHighlight();
10935     endingGame = 0;  /* [HGM] crash */
10936     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10937         if(matchMode == TRUE) { // match through command line: exit with or without popup
10938             if(ranking) {
10939                 ToNrEvent(forwardMostMove);
10940                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10941                 else ExitEvent(0);
10942             } else DisplayFatalError(buf, 0, 0);
10943         } else { // match through menu; just stop, with or without popup
10944             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10945             ModeHighlight();
10946             if(ranking){
10947                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10948             } else DisplayNote(buf);
10949       }
10950       if(ranking) free(ranking);
10951     }
10952 }
10953
10954 /* Assumes program was just initialized (initString sent).
10955    Leaves program in force mode. */
10956 void
10957 FeedMovesToProgram (ChessProgramState *cps, int upto)
10958 {
10959     int i;
10960
10961     if (appData.debugMode)
10962       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10963               startedFromSetupPosition ? "position and " : "",
10964               backwardMostMove, upto, cps->which);
10965     if(currentlyInitializedVariant != gameInfo.variant) {
10966       char buf[MSG_SIZ];
10967         // [HGM] variantswitch: make engine aware of new variant
10968         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10969                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10970         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10971         SendToProgram(buf, cps);
10972         currentlyInitializedVariant = gameInfo.variant;
10973     }
10974     SendToProgram("force\n", cps);
10975     if (startedFromSetupPosition) {
10976         SendBoard(cps, backwardMostMove);
10977     if (appData.debugMode) {
10978         fprintf(debugFP, "feedMoves\n");
10979     }
10980     }
10981     for (i = backwardMostMove; i < upto; i++) {
10982         SendMoveToProgram(i, cps);
10983     }
10984 }
10985
10986
10987 int
10988 ResurrectChessProgram ()
10989 {
10990      /* The chess program may have exited.
10991         If so, restart it and feed it all the moves made so far. */
10992     static int doInit = 0;
10993
10994     if (appData.noChessProgram) return 1;
10995
10996     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10997         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10998         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10999         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11000     } else {
11001         if (first.pr != NoProc) return 1;
11002         StartChessProgram(&first);
11003     }
11004     InitChessProgram(&first, FALSE);
11005     FeedMovesToProgram(&first, currentMove);
11006
11007     if (!first.sendTime) {
11008         /* can't tell gnuchess what its clock should read,
11009            so we bow to its notion. */
11010         ResetClocks();
11011         timeRemaining[0][currentMove] = whiteTimeRemaining;
11012         timeRemaining[1][currentMove] = blackTimeRemaining;
11013     }
11014
11015     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11016                 appData.icsEngineAnalyze) && first.analysisSupport) {
11017       SendToProgram("analyze\n", &first);
11018       first.analyzing = TRUE;
11019     }
11020     return 1;
11021 }
11022
11023 /*
11024  * Button procedures
11025  */
11026 void
11027 Reset (int redraw, int init)
11028 {
11029     int i;
11030
11031     if (appData.debugMode) {
11032         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11033                 redraw, init, gameMode);
11034     }
11035     CleanupTail(); // [HGM] vari: delete any stored variations
11036     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11037     pausing = pauseExamInvalid = FALSE;
11038     startedFromSetupPosition = blackPlaysFirst = FALSE;
11039     firstMove = TRUE;
11040     whiteFlag = blackFlag = FALSE;
11041     userOfferedDraw = FALSE;
11042     hintRequested = bookRequested = FALSE;
11043     first.maybeThinking = FALSE;
11044     second.maybeThinking = FALSE;
11045     first.bookSuspend = FALSE; // [HGM] book
11046     second.bookSuspend = FALSE;
11047     thinkOutput[0] = NULLCHAR;
11048     lastHint[0] = NULLCHAR;
11049     ClearGameInfo(&gameInfo);
11050     gameInfo.variant = StringToVariant(appData.variant);
11051     ics_user_moved = ics_clock_paused = FALSE;
11052     ics_getting_history = H_FALSE;
11053     ics_gamenum = -1;
11054     white_holding[0] = black_holding[0] = NULLCHAR;
11055     ClearProgramStats();
11056     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11057
11058     ResetFrontEnd();
11059     ClearHighlights();
11060     flipView = appData.flipView;
11061     ClearPremoveHighlights();
11062     gotPremove = FALSE;
11063     alarmSounded = FALSE;
11064
11065     GameEnds(EndOfFile, NULL, GE_PLAYER);
11066     if(appData.serverMovesName != NULL) {
11067         /* [HGM] prepare to make moves file for broadcasting */
11068         clock_t t = clock();
11069         if(serverMoves != NULL) fclose(serverMoves);
11070         serverMoves = fopen(appData.serverMovesName, "r");
11071         if(serverMoves != NULL) {
11072             fclose(serverMoves);
11073             /* delay 15 sec before overwriting, so all clients can see end */
11074             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11075         }
11076         serverMoves = fopen(appData.serverMovesName, "w");
11077     }
11078
11079     ExitAnalyzeMode();
11080     gameMode = BeginningOfGame;
11081     ModeHighlight();
11082     if(appData.icsActive) gameInfo.variant = VariantNormal;
11083     currentMove = forwardMostMove = backwardMostMove = 0;
11084     MarkTargetSquares(1);
11085     InitPosition(redraw);
11086     for (i = 0; i < MAX_MOVES; i++) {
11087         if (commentList[i] != NULL) {
11088             free(commentList[i]);
11089             commentList[i] = NULL;
11090         }
11091     }
11092     ResetClocks();
11093     timeRemaining[0][0] = whiteTimeRemaining;
11094     timeRemaining[1][0] = blackTimeRemaining;
11095
11096     if (first.pr == NoProc) {
11097         StartChessProgram(&first);
11098     }
11099     if (init) {
11100             InitChessProgram(&first, startedFromSetupPosition);
11101     }
11102     DisplayTitle("");
11103     DisplayMessage("", "");
11104     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11105     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11106     ClearMap();        // [HGM] exclude: invalidate map
11107 }
11108
11109 void
11110 AutoPlayGameLoop ()
11111 {
11112     for (;;) {
11113         if (!AutoPlayOneMove())
11114           return;
11115         if (matchMode || appData.timeDelay == 0)
11116           continue;
11117         if (appData.timeDelay < 0)
11118           return;
11119         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11120         break;
11121     }
11122 }
11123
11124 void
11125 AnalyzeNextGame()
11126 {
11127     ReloadGame(1); // next game
11128 }
11129
11130 int
11131 AutoPlayOneMove ()
11132 {
11133     int fromX, fromY, toX, toY;
11134
11135     if (appData.debugMode) {
11136       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11137     }
11138
11139     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11140       return FALSE;
11141
11142     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11143       pvInfoList[currentMove].depth = programStats.depth;
11144       pvInfoList[currentMove].score = programStats.score;
11145       pvInfoList[currentMove].time  = 0;
11146       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11147     }
11148
11149     if (currentMove >= forwardMostMove) {
11150       if(gameMode == AnalyzeFile) {
11151           if(appData.loadGameIndex == -1) {
11152             GameEnds(EndOfFile, NULL, GE_FILE);
11153           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11154           } else {
11155           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11156         }
11157       }
11158 //      gameMode = EndOfGame;
11159 //      ModeHighlight();
11160
11161       /* [AS] Clear current move marker at the end of a game */
11162       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11163
11164       return FALSE;
11165     }
11166
11167     toX = moveList[currentMove][2] - AAA;
11168     toY = moveList[currentMove][3] - ONE;
11169
11170     if (moveList[currentMove][1] == '@') {
11171         if (appData.highlightLastMove) {
11172             SetHighlights(-1, -1, toX, toY);
11173         }
11174     } else {
11175         fromX = moveList[currentMove][0] - AAA;
11176         fromY = moveList[currentMove][1] - ONE;
11177
11178         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11179
11180         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11181
11182         if (appData.highlightLastMove) {
11183             SetHighlights(fromX, fromY, toX, toY);
11184         }
11185     }
11186     DisplayMove(currentMove);
11187     SendMoveToProgram(currentMove++, &first);
11188     DisplayBothClocks();
11189     DrawPosition(FALSE, boards[currentMove]);
11190     // [HGM] PV info: always display, routine tests if empty
11191     DisplayComment(currentMove - 1, commentList[currentMove]);
11192     return TRUE;
11193 }
11194
11195
11196 int
11197 LoadGameOneMove (ChessMove readAhead)
11198 {
11199     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11200     char promoChar = NULLCHAR;
11201     ChessMove moveType;
11202     char move[MSG_SIZ];
11203     char *p, *q;
11204
11205     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11206         gameMode != AnalyzeMode && gameMode != Training) {
11207         gameFileFP = NULL;
11208         return FALSE;
11209     }
11210
11211     yyboardindex = forwardMostMove;
11212     if (readAhead != EndOfFile) {
11213       moveType = readAhead;
11214     } else {
11215       if (gameFileFP == NULL)
11216           return FALSE;
11217       moveType = (ChessMove) Myylex();
11218     }
11219
11220     done = FALSE;
11221     switch (moveType) {
11222       case Comment:
11223         if (appData.debugMode)
11224           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11225         p = yy_text;
11226
11227         /* append the comment but don't display it */
11228         AppendComment(currentMove, p, FALSE);
11229         return TRUE;
11230
11231       case WhiteCapturesEnPassant:
11232       case BlackCapturesEnPassant:
11233       case WhitePromotion:
11234       case BlackPromotion:
11235       case WhiteNonPromotion:
11236       case BlackNonPromotion:
11237       case NormalMove:
11238       case WhiteKingSideCastle:
11239       case WhiteQueenSideCastle:
11240       case BlackKingSideCastle:
11241       case BlackQueenSideCastle:
11242       case WhiteKingSideCastleWild:
11243       case WhiteQueenSideCastleWild:
11244       case BlackKingSideCastleWild:
11245       case BlackQueenSideCastleWild:
11246       /* PUSH Fabien */
11247       case WhiteHSideCastleFR:
11248       case WhiteASideCastleFR:
11249       case BlackHSideCastleFR:
11250       case BlackASideCastleFR:
11251       /* POP Fabien */
11252         if (appData.debugMode)
11253           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11254         fromX = currentMoveString[0] - AAA;
11255         fromY = currentMoveString[1] - ONE;
11256         toX = currentMoveString[2] - AAA;
11257         toY = currentMoveString[3] - ONE;
11258         promoChar = currentMoveString[4];
11259         break;
11260
11261       case WhiteDrop:
11262       case BlackDrop:
11263         if (appData.debugMode)
11264           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11265         fromX = moveType == WhiteDrop ?
11266           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11267         (int) CharToPiece(ToLower(currentMoveString[0]));
11268         fromY = DROP_RANK;
11269         toX = currentMoveString[2] - AAA;
11270         toY = currentMoveString[3] - ONE;
11271         break;
11272
11273       case WhiteWins:
11274       case BlackWins:
11275       case GameIsDrawn:
11276       case GameUnfinished:
11277         if (appData.debugMode)
11278           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11279         p = strchr(yy_text, '{');
11280         if (p == NULL) p = strchr(yy_text, '(');
11281         if (p == NULL) {
11282             p = yy_text;
11283             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11284         } else {
11285             q = strchr(p, *p == '{' ? '}' : ')');
11286             if (q != NULL) *q = NULLCHAR;
11287             p++;
11288         }
11289         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11290         GameEnds(moveType, p, GE_FILE);
11291         done = TRUE;
11292         if (cmailMsgLoaded) {
11293             ClearHighlights();
11294             flipView = WhiteOnMove(currentMove);
11295             if (moveType == GameUnfinished) flipView = !flipView;
11296             if (appData.debugMode)
11297               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11298         }
11299         break;
11300
11301       case EndOfFile:
11302         if (appData.debugMode)
11303           fprintf(debugFP, "Parser hit end of file\n");
11304         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11305           case MT_NONE:
11306           case MT_CHECK:
11307             break;
11308           case MT_CHECKMATE:
11309           case MT_STAINMATE:
11310             if (WhiteOnMove(currentMove)) {
11311                 GameEnds(BlackWins, "Black mates", GE_FILE);
11312             } else {
11313                 GameEnds(WhiteWins, "White mates", GE_FILE);
11314             }
11315             break;
11316           case MT_STALEMATE:
11317             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11318             break;
11319         }
11320         done = TRUE;
11321         break;
11322
11323       case MoveNumberOne:
11324         if (lastLoadGameStart == GNUChessGame) {
11325             /* GNUChessGames have numbers, but they aren't move numbers */
11326             if (appData.debugMode)
11327               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11328                       yy_text, (int) moveType);
11329             return LoadGameOneMove(EndOfFile); /* tail recursion */
11330         }
11331         /* else fall thru */
11332
11333       case XBoardGame:
11334       case GNUChessGame:
11335       case PGNTag:
11336         /* Reached start of next game in file */
11337         if (appData.debugMode)
11338           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11339         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11340           case MT_NONE:
11341           case MT_CHECK:
11342             break;
11343           case MT_CHECKMATE:
11344           case MT_STAINMATE:
11345             if (WhiteOnMove(currentMove)) {
11346                 GameEnds(BlackWins, "Black mates", GE_FILE);
11347             } else {
11348                 GameEnds(WhiteWins, "White mates", GE_FILE);
11349             }
11350             break;
11351           case MT_STALEMATE:
11352             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11353             break;
11354         }
11355         done = TRUE;
11356         break;
11357
11358       case PositionDiagram:     /* should not happen; ignore */
11359       case ElapsedTime:         /* ignore */
11360       case NAG:                 /* ignore */
11361         if (appData.debugMode)
11362           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11363                   yy_text, (int) moveType);
11364         return LoadGameOneMove(EndOfFile); /* tail recursion */
11365
11366       case IllegalMove:
11367         if (appData.testLegality) {
11368             if (appData.debugMode)
11369               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11370             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11371                     (forwardMostMove / 2) + 1,
11372                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11373             DisplayError(move, 0);
11374             done = TRUE;
11375         } else {
11376             if (appData.debugMode)
11377               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11378                       yy_text, currentMoveString);
11379             fromX = currentMoveString[0] - AAA;
11380             fromY = currentMoveString[1] - ONE;
11381             toX = currentMoveString[2] - AAA;
11382             toY = currentMoveString[3] - ONE;
11383             promoChar = currentMoveString[4];
11384         }
11385         break;
11386
11387       case AmbiguousMove:
11388         if (appData.debugMode)
11389           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11390         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11391                 (forwardMostMove / 2) + 1,
11392                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11393         DisplayError(move, 0);
11394         done = TRUE;
11395         break;
11396
11397       default:
11398       case ImpossibleMove:
11399         if (appData.debugMode)
11400           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11401         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11402                 (forwardMostMove / 2) + 1,
11403                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11404         DisplayError(move, 0);
11405         done = TRUE;
11406         break;
11407     }
11408
11409     if (done) {
11410         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11411             DrawPosition(FALSE, boards[currentMove]);
11412             DisplayBothClocks();
11413             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11414               DisplayComment(currentMove - 1, commentList[currentMove]);
11415         }
11416         (void) StopLoadGameTimer();
11417         gameFileFP = NULL;
11418         cmailOldMove = forwardMostMove;
11419         return FALSE;
11420     } else {
11421         /* currentMoveString is set as a side-effect of yylex */
11422
11423         thinkOutput[0] = NULLCHAR;
11424         MakeMove(fromX, fromY, toX, toY, promoChar);
11425         currentMove = forwardMostMove;
11426         return TRUE;
11427     }
11428 }
11429
11430 /* Load the nth game from the given file */
11431 int
11432 LoadGameFromFile (char *filename, int n, char *title, int useList)
11433 {
11434     FILE *f;
11435     char buf[MSG_SIZ];
11436
11437     if (strcmp(filename, "-") == 0) {
11438         f = stdin;
11439         title = "stdin";
11440     } else {
11441         f = fopen(filename, "rb");
11442         if (f == NULL) {
11443           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11444             DisplayError(buf, errno);
11445             return FALSE;
11446         }
11447     }
11448     if (fseek(f, 0, 0) == -1) {
11449         /* f is not seekable; probably a pipe */
11450         useList = FALSE;
11451     }
11452     if (useList && n == 0) {
11453         int error = GameListBuild(f);
11454         if (error) {
11455             DisplayError(_("Cannot build game list"), error);
11456         } else if (!ListEmpty(&gameList) &&
11457                    ((ListGame *) gameList.tailPred)->number > 1) {
11458             GameListPopUp(f, title);
11459             return TRUE;
11460         }
11461         GameListDestroy();
11462         n = 1;
11463     }
11464     if (n == 0) n = 1;
11465     return LoadGame(f, n, title, FALSE);
11466 }
11467
11468
11469 void
11470 MakeRegisteredMove ()
11471 {
11472     int fromX, fromY, toX, toY;
11473     char promoChar;
11474     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11475         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11476           case CMAIL_MOVE:
11477           case CMAIL_DRAW:
11478             if (appData.debugMode)
11479               fprintf(debugFP, "Restoring %s for game %d\n",
11480                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11481
11482             thinkOutput[0] = NULLCHAR;
11483             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11484             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11485             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11486             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11487             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11488             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11489             MakeMove(fromX, fromY, toX, toY, promoChar);
11490             ShowMove(fromX, fromY, toX, toY);
11491
11492             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11493               case MT_NONE:
11494               case MT_CHECK:
11495                 break;
11496
11497               case MT_CHECKMATE:
11498               case MT_STAINMATE:
11499                 if (WhiteOnMove(currentMove)) {
11500                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11501                 } else {
11502                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11503                 }
11504                 break;
11505
11506               case MT_STALEMATE:
11507                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11508                 break;
11509             }
11510
11511             break;
11512
11513           case CMAIL_RESIGN:
11514             if (WhiteOnMove(currentMove)) {
11515                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11516             } else {
11517                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11518             }
11519             break;
11520
11521           case CMAIL_ACCEPT:
11522             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11523             break;
11524
11525           default:
11526             break;
11527         }
11528     }
11529
11530     return;
11531 }
11532
11533 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11534 int
11535 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11536 {
11537     int retVal;
11538
11539     if (gameNumber > nCmailGames) {
11540         DisplayError(_("No more games in this message"), 0);
11541         return FALSE;
11542     }
11543     if (f == lastLoadGameFP) {
11544         int offset = gameNumber - lastLoadGameNumber;
11545         if (offset == 0) {
11546             cmailMsg[0] = NULLCHAR;
11547             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11548                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11549                 nCmailMovesRegistered--;
11550             }
11551             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11552             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11553                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11554             }
11555         } else {
11556             if (! RegisterMove()) return FALSE;
11557         }
11558     }
11559
11560     retVal = LoadGame(f, gameNumber, title, useList);
11561
11562     /* Make move registered during previous look at this game, if any */
11563     MakeRegisteredMove();
11564
11565     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11566         commentList[currentMove]
11567           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11568         DisplayComment(currentMove - 1, commentList[currentMove]);
11569     }
11570
11571     return retVal;
11572 }
11573
11574 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11575 int
11576 ReloadGame (int offset)
11577 {
11578     int gameNumber = lastLoadGameNumber + offset;
11579     if (lastLoadGameFP == NULL) {
11580         DisplayError(_("No game has been loaded yet"), 0);
11581         return FALSE;
11582     }
11583     if (gameNumber <= 0) {
11584         DisplayError(_("Can't back up any further"), 0);
11585         return FALSE;
11586     }
11587     if (cmailMsgLoaded) {
11588         return CmailLoadGame(lastLoadGameFP, gameNumber,
11589                              lastLoadGameTitle, lastLoadGameUseList);
11590     } else {
11591         return LoadGame(lastLoadGameFP, gameNumber,
11592                         lastLoadGameTitle, lastLoadGameUseList);
11593     }
11594 }
11595
11596 int keys[EmptySquare+1];
11597
11598 int
11599 PositionMatches (Board b1, Board b2)
11600 {
11601     int r, f, sum=0;
11602     switch(appData.searchMode) {
11603         case 1: return CompareWithRights(b1, b2);
11604         case 2:
11605             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11606                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11607             }
11608             return TRUE;
11609         case 3:
11610             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11611               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11612                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11613             }
11614             return sum==0;
11615         case 4:
11616             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11617                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11618             }
11619             return sum==0;
11620     }
11621     return TRUE;
11622 }
11623
11624 #define Q_PROMO  4
11625 #define Q_EP     3
11626 #define Q_BCASTL 2
11627 #define Q_WCASTL 1
11628
11629 int pieceList[256], quickBoard[256];
11630 ChessSquare pieceType[256] = { EmptySquare };
11631 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11632 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11633 int soughtTotal, turn;
11634 Boolean epOK, flipSearch;
11635
11636 typedef struct {
11637     unsigned char piece, to;
11638 } Move;
11639
11640 #define DSIZE (250000)
11641
11642 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11643 Move *moveDatabase = initialSpace;
11644 unsigned int movePtr, dataSize = DSIZE;
11645
11646 int
11647 MakePieceList (Board board, int *counts)
11648 {
11649     int r, f, n=Q_PROMO, total=0;
11650     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11651     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11652         int sq = f + (r<<4);
11653         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11654             quickBoard[sq] = ++n;
11655             pieceList[n] = sq;
11656             pieceType[n] = board[r][f];
11657             counts[board[r][f]]++;
11658             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11659             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11660             total++;
11661         }
11662     }
11663     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11664     return total;
11665 }
11666
11667 void
11668 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11669 {
11670     int sq = fromX + (fromY<<4);
11671     int piece = quickBoard[sq];
11672     quickBoard[sq] = 0;
11673     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11674     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11675         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11676         moveDatabase[movePtr++].piece = Q_WCASTL;
11677         quickBoard[sq] = piece;
11678         piece = quickBoard[from]; quickBoard[from] = 0;
11679         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11680     } else
11681     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11682         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11683         moveDatabase[movePtr++].piece = Q_BCASTL;
11684         quickBoard[sq] = piece;
11685         piece = quickBoard[from]; quickBoard[from] = 0;
11686         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11687     } else
11688     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11689         quickBoard[(fromY<<4)+toX] = 0;
11690         moveDatabase[movePtr].piece = Q_EP;
11691         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11692         moveDatabase[movePtr].to = sq;
11693     } else
11694     if(promoPiece != pieceType[piece]) {
11695         moveDatabase[movePtr++].piece = Q_PROMO;
11696         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11697     }
11698     moveDatabase[movePtr].piece = piece;
11699     quickBoard[sq] = piece;
11700     movePtr++;
11701 }
11702
11703 int
11704 PackGame (Board board)
11705 {
11706     Move *newSpace = NULL;
11707     moveDatabase[movePtr].piece = 0; // terminate previous game
11708     if(movePtr > dataSize) {
11709         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11710         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11711         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11712         if(newSpace) {
11713             int i;
11714             Move *p = moveDatabase, *q = newSpace;
11715             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11716             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11717             moveDatabase = newSpace;
11718         } else { // calloc failed, we must be out of memory. Too bad...
11719             dataSize = 0; // prevent calloc events for all subsequent games
11720             return 0;     // and signal this one isn't cached
11721         }
11722     }
11723     movePtr++;
11724     MakePieceList(board, counts);
11725     return movePtr;
11726 }
11727
11728 int
11729 QuickCompare (Board board, int *minCounts, int *maxCounts)
11730 {   // compare according to search mode
11731     int r, f;
11732     switch(appData.searchMode)
11733     {
11734       case 1: // exact position match
11735         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11736         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11737             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11738         }
11739         break;
11740       case 2: // can have extra material on empty squares
11741         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11742             if(board[r][f] == EmptySquare) continue;
11743             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11744         }
11745         break;
11746       case 3: // material with exact Pawn structure
11747         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11748             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11749             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11750         } // fall through to material comparison
11751       case 4: // exact material
11752         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11753         break;
11754       case 6: // material range with given imbalance
11755         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11756         // fall through to range comparison
11757       case 5: // material range
11758         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11759     }
11760     return TRUE;
11761 }
11762
11763 int
11764 QuickScan (Board board, Move *move)
11765 {   // reconstruct game,and compare all positions in it
11766     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11767     do {
11768         int piece = move->piece;
11769         int to = move->to, from = pieceList[piece];
11770         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11771           if(!piece) return -1;
11772           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11773             piece = (++move)->piece;
11774             from = pieceList[piece];
11775             counts[pieceType[piece]]--;
11776             pieceType[piece] = (ChessSquare) move->to;
11777             counts[move->to]++;
11778           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11779             counts[pieceType[quickBoard[to]]]--;
11780             quickBoard[to] = 0; total--;
11781             move++;
11782             continue;
11783           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11784             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11785             from  = pieceList[piece]; // so this must be King
11786             quickBoard[from] = 0;
11787             pieceList[piece] = to;
11788             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11789             quickBoard[from] = 0; // rook
11790             quickBoard[to] = piece;
11791             to = move->to; piece = move->piece;
11792             goto aftercastle;
11793           }
11794         }
11795         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11796         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11797         quickBoard[from] = 0;
11798       aftercastle:
11799         quickBoard[to] = piece;
11800         pieceList[piece] = to;
11801         cnt++; turn ^= 3;
11802         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11803            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11804            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11805                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11806           ) {
11807             static int lastCounts[EmptySquare+1];
11808             int i;
11809             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11810             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11811         } else stretch = 0;
11812         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11813         move++;
11814     } while(1);
11815 }
11816
11817 void
11818 InitSearch ()
11819 {
11820     int r, f;
11821     flipSearch = FALSE;
11822     CopyBoard(soughtBoard, boards[currentMove]);
11823     soughtTotal = MakePieceList(soughtBoard, maxSought);
11824     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11825     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11826     CopyBoard(reverseBoard, boards[currentMove]);
11827     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11828         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11829         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11830         reverseBoard[r][f] = piece;
11831     }
11832     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11833     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11834     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11835                  || (boards[currentMove][CASTLING][2] == NoRights ||
11836                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11837                  && (boards[currentMove][CASTLING][5] == NoRights ||
11838                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11839       ) {
11840         flipSearch = TRUE;
11841         CopyBoard(flipBoard, soughtBoard);
11842         CopyBoard(rotateBoard, reverseBoard);
11843         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11844             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11845             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11846         }
11847     }
11848     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11849     if(appData.searchMode >= 5) {
11850         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11851         MakePieceList(soughtBoard, minSought);
11852         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11853     }
11854     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11855         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11856 }
11857
11858 GameInfo dummyInfo;
11859 static int creatingBook;
11860
11861 int
11862 GameContainsPosition (FILE *f, ListGame *lg)
11863 {
11864     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11865     int fromX, fromY, toX, toY;
11866     char promoChar;
11867     static int initDone=FALSE;
11868
11869     // weed out games based on numerical tag comparison
11870     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11871     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11872     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11873     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11874     if(!initDone) {
11875         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11876         initDone = TRUE;
11877     }
11878     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11879     else CopyBoard(boards[scratch], initialPosition); // default start position
11880     if(lg->moves) {
11881         turn = btm + 1;
11882         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11883         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11884     }
11885     if(btm) plyNr++;
11886     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11887     fseek(f, lg->offset, 0);
11888     yynewfile(f);
11889     while(1) {
11890         yyboardindex = scratch;
11891         quickFlag = plyNr+1;
11892         next = Myylex();
11893         quickFlag = 0;
11894         switch(next) {
11895             case PGNTag:
11896                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11897             default:
11898                 continue;
11899
11900             case XBoardGame:
11901             case GNUChessGame:
11902                 if(plyNr) return -1; // after we have seen moves, this is for new game
11903               continue;
11904
11905             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11906             case ImpossibleMove:
11907             case WhiteWins: // game ends here with these four
11908             case BlackWins:
11909             case GameIsDrawn:
11910             case GameUnfinished:
11911                 return -1;
11912
11913             case IllegalMove:
11914                 if(appData.testLegality) return -1;
11915             case WhiteCapturesEnPassant:
11916             case BlackCapturesEnPassant:
11917             case WhitePromotion:
11918             case BlackPromotion:
11919             case WhiteNonPromotion:
11920             case BlackNonPromotion:
11921             case NormalMove:
11922             case WhiteKingSideCastle:
11923             case WhiteQueenSideCastle:
11924             case BlackKingSideCastle:
11925             case BlackQueenSideCastle:
11926             case WhiteKingSideCastleWild:
11927             case WhiteQueenSideCastleWild:
11928             case BlackKingSideCastleWild:
11929             case BlackQueenSideCastleWild:
11930             case WhiteHSideCastleFR:
11931             case WhiteASideCastleFR:
11932             case BlackHSideCastleFR:
11933             case BlackASideCastleFR:
11934                 fromX = currentMoveString[0] - AAA;
11935                 fromY = currentMoveString[1] - ONE;
11936                 toX = currentMoveString[2] - AAA;
11937                 toY = currentMoveString[3] - ONE;
11938                 promoChar = currentMoveString[4];
11939                 break;
11940             case WhiteDrop:
11941             case BlackDrop:
11942                 fromX = next == WhiteDrop ?
11943                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11944                   (int) CharToPiece(ToLower(currentMoveString[0]));
11945                 fromY = DROP_RANK;
11946                 toX = currentMoveString[2] - AAA;
11947                 toY = currentMoveString[3] - ONE;
11948                 promoChar = 0;
11949                 break;
11950         }
11951         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11952         plyNr++;
11953         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11954         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11955         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11956         if(appData.findMirror) {
11957             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11958             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11959         }
11960     }
11961 }
11962
11963 /* Load the nth game from open file f */
11964 int
11965 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11966 {
11967     ChessMove cm;
11968     char buf[MSG_SIZ];
11969     int gn = gameNumber;
11970     ListGame *lg = NULL;
11971     int numPGNTags = 0;
11972     int err, pos = -1;
11973     GameMode oldGameMode;
11974     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11975
11976     if (appData.debugMode)
11977         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11978
11979     if (gameMode == Training )
11980         SetTrainingModeOff();
11981
11982     oldGameMode = gameMode;
11983     if (gameMode != BeginningOfGame) {
11984       Reset(FALSE, TRUE);
11985     }
11986
11987     gameFileFP = f;
11988     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11989         fclose(lastLoadGameFP);
11990     }
11991
11992     if (useList) {
11993         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11994
11995         if (lg) {
11996             fseek(f, lg->offset, 0);
11997             GameListHighlight(gameNumber);
11998             pos = lg->position;
11999             gn = 1;
12000         }
12001         else {
12002             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12003               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12004             else
12005             DisplayError(_("Game number out of range"), 0);
12006             return FALSE;
12007         }
12008     } else {
12009         GameListDestroy();
12010         if (fseek(f, 0, 0) == -1) {
12011             if (f == lastLoadGameFP ?
12012                 gameNumber == lastLoadGameNumber + 1 :
12013                 gameNumber == 1) {
12014                 gn = 1;
12015             } else {
12016                 DisplayError(_("Can't seek on game file"), 0);
12017                 return FALSE;
12018             }
12019         }
12020     }
12021     lastLoadGameFP = f;
12022     lastLoadGameNumber = gameNumber;
12023     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12024     lastLoadGameUseList = useList;
12025
12026     yynewfile(f);
12027
12028     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12029       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12030                 lg->gameInfo.black);
12031             DisplayTitle(buf);
12032     } else if (*title != NULLCHAR) {
12033         if (gameNumber > 1) {
12034           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12035             DisplayTitle(buf);
12036         } else {
12037             DisplayTitle(title);
12038         }
12039     }
12040
12041     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12042         gameMode = PlayFromGameFile;
12043         ModeHighlight();
12044     }
12045
12046     currentMove = forwardMostMove = backwardMostMove = 0;
12047     CopyBoard(boards[0], initialPosition);
12048     StopClocks();
12049
12050     /*
12051      * Skip the first gn-1 games in the file.
12052      * Also skip over anything that precedes an identifiable
12053      * start of game marker, to avoid being confused by
12054      * garbage at the start of the file.  Currently
12055      * recognized start of game markers are the move number "1",
12056      * the pattern "gnuchess .* game", the pattern
12057      * "^[#;%] [^ ]* game file", and a PGN tag block.
12058      * A game that starts with one of the latter two patterns
12059      * will also have a move number 1, possibly
12060      * following a position diagram.
12061      * 5-4-02: Let's try being more lenient and allowing a game to
12062      * start with an unnumbered move.  Does that break anything?
12063      */
12064     cm = lastLoadGameStart = EndOfFile;
12065     while (gn > 0) {
12066         yyboardindex = forwardMostMove;
12067         cm = (ChessMove) Myylex();
12068         switch (cm) {
12069           case EndOfFile:
12070             if (cmailMsgLoaded) {
12071                 nCmailGames = CMAIL_MAX_GAMES - gn;
12072             } else {
12073                 Reset(TRUE, TRUE);
12074                 DisplayError(_("Game not found in file"), 0);
12075             }
12076             return FALSE;
12077
12078           case GNUChessGame:
12079           case XBoardGame:
12080             gn--;
12081             lastLoadGameStart = cm;
12082             break;
12083
12084           case MoveNumberOne:
12085             switch (lastLoadGameStart) {
12086               case GNUChessGame:
12087               case XBoardGame:
12088               case PGNTag:
12089                 break;
12090               case MoveNumberOne:
12091               case EndOfFile:
12092                 gn--;           /* count this game */
12093                 lastLoadGameStart = cm;
12094                 break;
12095               default:
12096                 /* impossible */
12097                 break;
12098             }
12099             break;
12100
12101           case PGNTag:
12102             switch (lastLoadGameStart) {
12103               case GNUChessGame:
12104               case PGNTag:
12105               case MoveNumberOne:
12106               case EndOfFile:
12107                 gn--;           /* count this game */
12108                 lastLoadGameStart = cm;
12109                 break;
12110               case XBoardGame:
12111                 lastLoadGameStart = cm; /* game counted already */
12112                 break;
12113               default:
12114                 /* impossible */
12115                 break;
12116             }
12117             if (gn > 0) {
12118                 do {
12119                     yyboardindex = forwardMostMove;
12120                     cm = (ChessMove) Myylex();
12121                 } while (cm == PGNTag || cm == Comment);
12122             }
12123             break;
12124
12125           case WhiteWins:
12126           case BlackWins:
12127           case GameIsDrawn:
12128             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12129                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12130                     != CMAIL_OLD_RESULT) {
12131                     nCmailResults ++ ;
12132                     cmailResult[  CMAIL_MAX_GAMES
12133                                 - gn - 1] = CMAIL_OLD_RESULT;
12134                 }
12135             }
12136             break;
12137
12138           case NormalMove:
12139             /* Only a NormalMove can be at the start of a game
12140              * without a position diagram. */
12141             if (lastLoadGameStart == EndOfFile ) {
12142               gn--;
12143               lastLoadGameStart = MoveNumberOne;
12144             }
12145             break;
12146
12147           default:
12148             break;
12149         }
12150     }
12151
12152     if (appData.debugMode)
12153       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12154
12155     if (cm == XBoardGame) {
12156         /* Skip any header junk before position diagram and/or move 1 */
12157         for (;;) {
12158             yyboardindex = forwardMostMove;
12159             cm = (ChessMove) Myylex();
12160
12161             if (cm == EndOfFile ||
12162                 cm == GNUChessGame || cm == XBoardGame) {
12163                 /* Empty game; pretend end-of-file and handle later */
12164                 cm = EndOfFile;
12165                 break;
12166             }
12167
12168             if (cm == MoveNumberOne || cm == PositionDiagram ||
12169                 cm == PGNTag || cm == Comment)
12170               break;
12171         }
12172     } else if (cm == GNUChessGame) {
12173         if (gameInfo.event != NULL) {
12174             free(gameInfo.event);
12175         }
12176         gameInfo.event = StrSave(yy_text);
12177     }
12178
12179     startedFromSetupPosition = FALSE;
12180     while (cm == PGNTag) {
12181         if (appData.debugMode)
12182           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12183         err = ParsePGNTag(yy_text, &gameInfo);
12184         if (!err) numPGNTags++;
12185
12186         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12187         if(gameInfo.variant != oldVariant) {
12188             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12189             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12190             InitPosition(TRUE);
12191             oldVariant = gameInfo.variant;
12192             if (appData.debugMode)
12193               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12194         }
12195
12196
12197         if (gameInfo.fen != NULL) {
12198           Board initial_position;
12199           startedFromSetupPosition = TRUE;
12200           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12201             Reset(TRUE, TRUE);
12202             DisplayError(_("Bad FEN position in file"), 0);
12203             return FALSE;
12204           }
12205           CopyBoard(boards[0], initial_position);
12206           if (blackPlaysFirst) {
12207             currentMove = forwardMostMove = backwardMostMove = 1;
12208             CopyBoard(boards[1], initial_position);
12209             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12210             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12211             timeRemaining[0][1] = whiteTimeRemaining;
12212             timeRemaining[1][1] = blackTimeRemaining;
12213             if (commentList[0] != NULL) {
12214               commentList[1] = commentList[0];
12215               commentList[0] = NULL;
12216             }
12217           } else {
12218             currentMove = forwardMostMove = backwardMostMove = 0;
12219           }
12220           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12221           {   int i;
12222               initialRulePlies = FENrulePlies;
12223               for( i=0; i< nrCastlingRights; i++ )
12224                   initialRights[i] = initial_position[CASTLING][i];
12225           }
12226           yyboardindex = forwardMostMove;
12227           free(gameInfo.fen);
12228           gameInfo.fen = NULL;
12229         }
12230
12231         yyboardindex = forwardMostMove;
12232         cm = (ChessMove) Myylex();
12233
12234         /* Handle comments interspersed among the tags */
12235         while (cm == Comment) {
12236             char *p;
12237             if (appData.debugMode)
12238               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12239             p = yy_text;
12240             AppendComment(currentMove, p, FALSE);
12241             yyboardindex = forwardMostMove;
12242             cm = (ChessMove) Myylex();
12243         }
12244     }
12245
12246     /* don't rely on existence of Event tag since if game was
12247      * pasted from clipboard the Event tag may not exist
12248      */
12249     if (numPGNTags > 0){
12250         char *tags;
12251         if (gameInfo.variant == VariantNormal) {
12252           VariantClass v = StringToVariant(gameInfo.event);
12253           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12254           if(v < VariantShogi) gameInfo.variant = v;
12255         }
12256         if (!matchMode) {
12257           if( appData.autoDisplayTags ) {
12258             tags = PGNTags(&gameInfo);
12259             TagsPopUp(tags, CmailMsg());
12260             free(tags);
12261           }
12262         }
12263     } else {
12264         /* Make something up, but don't display it now */
12265         SetGameInfo();
12266         TagsPopDown();
12267     }
12268
12269     if (cm == PositionDiagram) {
12270         int i, j;
12271         char *p;
12272         Board initial_position;
12273
12274         if (appData.debugMode)
12275           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12276
12277         if (!startedFromSetupPosition) {
12278             p = yy_text;
12279             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12280               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12281                 switch (*p) {
12282                   case '{':
12283                   case '[':
12284                   case '-':
12285                   case ' ':
12286                   case '\t':
12287                   case '\n':
12288                   case '\r':
12289                     break;
12290                   default:
12291                     initial_position[i][j++] = CharToPiece(*p);
12292                     break;
12293                 }
12294             while (*p == ' ' || *p == '\t' ||
12295                    *p == '\n' || *p == '\r') p++;
12296
12297             if (strncmp(p, "black", strlen("black"))==0)
12298               blackPlaysFirst = TRUE;
12299             else
12300               blackPlaysFirst = FALSE;
12301             startedFromSetupPosition = TRUE;
12302
12303             CopyBoard(boards[0], initial_position);
12304             if (blackPlaysFirst) {
12305                 currentMove = forwardMostMove = backwardMostMove = 1;
12306                 CopyBoard(boards[1], initial_position);
12307                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12308                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12309                 timeRemaining[0][1] = whiteTimeRemaining;
12310                 timeRemaining[1][1] = blackTimeRemaining;
12311                 if (commentList[0] != NULL) {
12312                     commentList[1] = commentList[0];
12313                     commentList[0] = NULL;
12314                 }
12315             } else {
12316                 currentMove = forwardMostMove = backwardMostMove = 0;
12317             }
12318         }
12319         yyboardindex = forwardMostMove;
12320         cm = (ChessMove) Myylex();
12321     }
12322
12323   if(!creatingBook) {
12324     if (first.pr == NoProc) {
12325         StartChessProgram(&first);
12326     }
12327     InitChessProgram(&first, FALSE);
12328     SendToProgram("force\n", &first);
12329     if (startedFromSetupPosition) {
12330         SendBoard(&first, forwardMostMove);
12331     if (appData.debugMode) {
12332         fprintf(debugFP, "Load Game\n");
12333     }
12334         DisplayBothClocks();
12335     }
12336   }
12337
12338     /* [HGM] server: flag to write setup moves in broadcast file as one */
12339     loadFlag = appData.suppressLoadMoves;
12340
12341     while (cm == Comment) {
12342         char *p;
12343         if (appData.debugMode)
12344           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12345         p = yy_text;
12346         AppendComment(currentMove, p, FALSE);
12347         yyboardindex = forwardMostMove;
12348         cm = (ChessMove) Myylex();
12349     }
12350
12351     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12352         cm == WhiteWins || cm == BlackWins ||
12353         cm == GameIsDrawn || cm == GameUnfinished) {
12354         DisplayMessage("", _("No moves in game"));
12355         if (cmailMsgLoaded) {
12356             if (appData.debugMode)
12357               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12358             ClearHighlights();
12359             flipView = FALSE;
12360         }
12361         DrawPosition(FALSE, boards[currentMove]);
12362         DisplayBothClocks();
12363         gameMode = EditGame;
12364         ModeHighlight();
12365         gameFileFP = NULL;
12366         cmailOldMove = 0;
12367         return TRUE;
12368     }
12369
12370     // [HGM] PV info: routine tests if comment empty
12371     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12372         DisplayComment(currentMove - 1, commentList[currentMove]);
12373     }
12374     if (!matchMode && appData.timeDelay != 0)
12375       DrawPosition(FALSE, boards[currentMove]);
12376
12377     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12378       programStats.ok_to_send = 1;
12379     }
12380
12381     /* if the first token after the PGN tags is a move
12382      * and not move number 1, retrieve it from the parser
12383      */
12384     if (cm != MoveNumberOne)
12385         LoadGameOneMove(cm);
12386
12387     /* load the remaining moves from the file */
12388     while (LoadGameOneMove(EndOfFile)) {
12389       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12390       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12391     }
12392
12393     /* rewind to the start of the game */
12394     currentMove = backwardMostMove;
12395
12396     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12397
12398     if (oldGameMode == AnalyzeFile ||
12399         oldGameMode == AnalyzeMode) {
12400       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12401       AnalyzeFileEvent();
12402     }
12403
12404     if(creatingBook) return TRUE;
12405     if (!matchMode && pos > 0) {
12406         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12407     } else
12408     if (matchMode || appData.timeDelay == 0) {
12409       ToEndEvent();
12410     } else if (appData.timeDelay > 0) {
12411       AutoPlayGameLoop();
12412     }
12413
12414     if (appData.debugMode)
12415         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12416
12417     loadFlag = 0; /* [HGM] true game starts */
12418     return TRUE;
12419 }
12420
12421 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12422 int
12423 ReloadPosition (int offset)
12424 {
12425     int positionNumber = lastLoadPositionNumber + offset;
12426     if (lastLoadPositionFP == NULL) {
12427         DisplayError(_("No position has been loaded yet"), 0);
12428         return FALSE;
12429     }
12430     if (positionNumber <= 0) {
12431         DisplayError(_("Can't back up any further"), 0);
12432         return FALSE;
12433     }
12434     return LoadPosition(lastLoadPositionFP, positionNumber,
12435                         lastLoadPositionTitle);
12436 }
12437
12438 /* Load the nth position from the given file */
12439 int
12440 LoadPositionFromFile (char *filename, int n, char *title)
12441 {
12442     FILE *f;
12443     char buf[MSG_SIZ];
12444
12445     if (strcmp(filename, "-") == 0) {
12446         return LoadPosition(stdin, n, "stdin");
12447     } else {
12448         f = fopen(filename, "rb");
12449         if (f == NULL) {
12450             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12451             DisplayError(buf, errno);
12452             return FALSE;
12453         } else {
12454             return LoadPosition(f, n, title);
12455         }
12456     }
12457 }
12458
12459 /* Load the nth position from the given open file, and close it */
12460 int
12461 LoadPosition (FILE *f, int positionNumber, char *title)
12462 {
12463     char *p, line[MSG_SIZ];
12464     Board initial_position;
12465     int i, j, fenMode, pn;
12466
12467     if (gameMode == Training )
12468         SetTrainingModeOff();
12469
12470     if (gameMode != BeginningOfGame) {
12471         Reset(FALSE, TRUE);
12472     }
12473     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12474         fclose(lastLoadPositionFP);
12475     }
12476     if (positionNumber == 0) positionNumber = 1;
12477     lastLoadPositionFP = f;
12478     lastLoadPositionNumber = positionNumber;
12479     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12480     if (first.pr == NoProc && !appData.noChessProgram) {
12481       StartChessProgram(&first);
12482       InitChessProgram(&first, FALSE);
12483     }
12484     pn = positionNumber;
12485     if (positionNumber < 0) {
12486         /* Negative position number means to seek to that byte offset */
12487         if (fseek(f, -positionNumber, 0) == -1) {
12488             DisplayError(_("Can't seek on position file"), 0);
12489             return FALSE;
12490         };
12491         pn = 1;
12492     } else {
12493         if (fseek(f, 0, 0) == -1) {
12494             if (f == lastLoadPositionFP ?
12495                 positionNumber == lastLoadPositionNumber + 1 :
12496                 positionNumber == 1) {
12497                 pn = 1;
12498             } else {
12499                 DisplayError(_("Can't seek on position file"), 0);
12500                 return FALSE;
12501             }
12502         }
12503     }
12504     /* See if this file is FEN or old-style xboard */
12505     if (fgets(line, MSG_SIZ, f) == NULL) {
12506         DisplayError(_("Position not found in file"), 0);
12507         return FALSE;
12508     }
12509     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12510     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12511
12512     if (pn >= 2) {
12513         if (fenMode || line[0] == '#') pn--;
12514         while (pn > 0) {
12515             /* skip positions before number pn */
12516             if (fgets(line, MSG_SIZ, f) == NULL) {
12517                 Reset(TRUE, TRUE);
12518                 DisplayError(_("Position not found in file"), 0);
12519                 return FALSE;
12520             }
12521             if (fenMode || line[0] == '#') pn--;
12522         }
12523     }
12524
12525     if (fenMode) {
12526         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12527             DisplayError(_("Bad FEN position in file"), 0);
12528             return FALSE;
12529         }
12530     } else {
12531         (void) fgets(line, MSG_SIZ, f);
12532         (void) fgets(line, MSG_SIZ, f);
12533
12534         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12535             (void) fgets(line, MSG_SIZ, f);
12536             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12537                 if (*p == ' ')
12538                   continue;
12539                 initial_position[i][j++] = CharToPiece(*p);
12540             }
12541         }
12542
12543         blackPlaysFirst = FALSE;
12544         if (!feof(f)) {
12545             (void) fgets(line, MSG_SIZ, f);
12546             if (strncmp(line, "black", strlen("black"))==0)
12547               blackPlaysFirst = TRUE;
12548         }
12549     }
12550     startedFromSetupPosition = TRUE;
12551
12552     CopyBoard(boards[0], initial_position);
12553     if (blackPlaysFirst) {
12554         currentMove = forwardMostMove = backwardMostMove = 1;
12555         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12556         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12557         CopyBoard(boards[1], initial_position);
12558         DisplayMessage("", _("Black to play"));
12559     } else {
12560         currentMove = forwardMostMove = backwardMostMove = 0;
12561         DisplayMessage("", _("White to play"));
12562     }
12563     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12564     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12565         SendToProgram("force\n", &first);
12566         SendBoard(&first, forwardMostMove);
12567     }
12568     if (appData.debugMode) {
12569 int i, j;
12570   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12571   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12572         fprintf(debugFP, "Load Position\n");
12573     }
12574
12575     if (positionNumber > 1) {
12576       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12577         DisplayTitle(line);
12578     } else {
12579         DisplayTitle(title);
12580     }
12581     gameMode = EditGame;
12582     ModeHighlight();
12583     ResetClocks();
12584     timeRemaining[0][1] = whiteTimeRemaining;
12585     timeRemaining[1][1] = blackTimeRemaining;
12586     DrawPosition(FALSE, boards[currentMove]);
12587
12588     return TRUE;
12589 }
12590
12591
12592 void
12593 CopyPlayerNameIntoFileName (char **dest, char *src)
12594 {
12595     while (*src != NULLCHAR && *src != ',') {
12596         if (*src == ' ') {
12597             *(*dest)++ = '_';
12598             src++;
12599         } else {
12600             *(*dest)++ = *src++;
12601         }
12602     }
12603 }
12604
12605 char *
12606 DefaultFileName (char *ext)
12607 {
12608     static char def[MSG_SIZ];
12609     char *p;
12610
12611     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12612         p = def;
12613         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12614         *p++ = '-';
12615         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12616         *p++ = '.';
12617         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12618     } else {
12619         def[0] = NULLCHAR;
12620     }
12621     return def;
12622 }
12623
12624 /* Save the current game to the given file */
12625 int
12626 SaveGameToFile (char *filename, int append)
12627 {
12628     FILE *f;
12629     char buf[MSG_SIZ];
12630     int result, i, t,tot=0;
12631
12632     if (strcmp(filename, "-") == 0) {
12633         return SaveGame(stdout, 0, NULL);
12634     } else {
12635         for(i=0; i<10; i++) { // upto 10 tries
12636              f = fopen(filename, append ? "a" : "w");
12637              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12638              if(f || errno != 13) break;
12639              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12640              tot += t;
12641         }
12642         if (f == NULL) {
12643             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12644             DisplayError(buf, errno);
12645             return FALSE;
12646         } else {
12647             safeStrCpy(buf, lastMsg, MSG_SIZ);
12648             DisplayMessage(_("Waiting for access to save file"), "");
12649             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12650             DisplayMessage(_("Saving game"), "");
12651             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12652             result = SaveGame(f, 0, NULL);
12653             DisplayMessage(buf, "");
12654             return result;
12655         }
12656     }
12657 }
12658
12659 char *
12660 SavePart (char *str)
12661 {
12662     static char buf[MSG_SIZ];
12663     char *p;
12664
12665     p = strchr(str, ' ');
12666     if (p == NULL) return str;
12667     strncpy(buf, str, p - str);
12668     buf[p - str] = NULLCHAR;
12669     return buf;
12670 }
12671
12672 #define PGN_MAX_LINE 75
12673
12674 #define PGN_SIDE_WHITE  0
12675 #define PGN_SIDE_BLACK  1
12676
12677 static int
12678 FindFirstMoveOutOfBook (int side)
12679 {
12680     int result = -1;
12681
12682     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12683         int index = backwardMostMove;
12684         int has_book_hit = 0;
12685
12686         if( (index % 2) != side ) {
12687             index++;
12688         }
12689
12690         while( index < forwardMostMove ) {
12691             /* Check to see if engine is in book */
12692             int depth = pvInfoList[index].depth;
12693             int score = pvInfoList[index].score;
12694             int in_book = 0;
12695
12696             if( depth <= 2 ) {
12697                 in_book = 1;
12698             }
12699             else if( score == 0 && depth == 63 ) {
12700                 in_book = 1; /* Zappa */
12701             }
12702             else if( score == 2 && depth == 99 ) {
12703                 in_book = 1; /* Abrok */
12704             }
12705
12706             has_book_hit += in_book;
12707
12708             if( ! in_book ) {
12709                 result = index;
12710
12711                 break;
12712             }
12713
12714             index += 2;
12715         }
12716     }
12717
12718     return result;
12719 }
12720
12721 void
12722 GetOutOfBookInfo (char * buf)
12723 {
12724     int oob[2];
12725     int i;
12726     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12727
12728     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12729     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12730
12731     *buf = '\0';
12732
12733     if( oob[0] >= 0 || oob[1] >= 0 ) {
12734         for( i=0; i<2; i++ ) {
12735             int idx = oob[i];
12736
12737             if( idx >= 0 ) {
12738                 if( i > 0 && oob[0] >= 0 ) {
12739                     strcat( buf, "   " );
12740                 }
12741
12742                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12743                 sprintf( buf+strlen(buf), "%s%.2f",
12744                     pvInfoList[idx].score >= 0 ? "+" : "",
12745                     pvInfoList[idx].score / 100.0 );
12746             }
12747         }
12748     }
12749 }
12750
12751 /* Save game in PGN style and close the file */
12752 int
12753 SaveGamePGN (FILE *f)
12754 {
12755     int i, offset, linelen, newblock;
12756 //    char *movetext;
12757     char numtext[32];
12758     int movelen, numlen, blank;
12759     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12760
12761     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12762
12763     PrintPGNTags(f, &gameInfo);
12764
12765     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12766
12767     if (backwardMostMove > 0 || startedFromSetupPosition) {
12768         char *fen = PositionToFEN(backwardMostMove, NULL);
12769         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12770         fprintf(f, "\n{--------------\n");
12771         PrintPosition(f, backwardMostMove);
12772         fprintf(f, "--------------}\n");
12773         free(fen);
12774     }
12775     else {
12776         /* [AS] Out of book annotation */
12777         if( appData.saveOutOfBookInfo ) {
12778             char buf[64];
12779
12780             GetOutOfBookInfo( buf );
12781
12782             if( buf[0] != '\0' ) {
12783                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12784             }
12785         }
12786
12787         fprintf(f, "\n");
12788     }
12789
12790     i = backwardMostMove;
12791     linelen = 0;
12792     newblock = TRUE;
12793
12794     while (i < forwardMostMove) {
12795         /* Print comments preceding this move */
12796         if (commentList[i] != NULL) {
12797             if (linelen > 0) fprintf(f, "\n");
12798             fprintf(f, "%s", commentList[i]);
12799             linelen = 0;
12800             newblock = TRUE;
12801         }
12802
12803         /* Format move number */
12804         if ((i % 2) == 0)
12805           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12806         else
12807           if (newblock)
12808             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12809           else
12810             numtext[0] = NULLCHAR;
12811
12812         numlen = strlen(numtext);
12813         newblock = FALSE;
12814
12815         /* Print move number */
12816         blank = linelen > 0 && numlen > 0;
12817         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12818             fprintf(f, "\n");
12819             linelen = 0;
12820             blank = 0;
12821         }
12822         if (blank) {
12823             fprintf(f, " ");
12824             linelen++;
12825         }
12826         fprintf(f, "%s", numtext);
12827         linelen += numlen;
12828
12829         /* Get move */
12830         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12831         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12832
12833         /* Print move */
12834         blank = linelen > 0 && movelen > 0;
12835         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12836             fprintf(f, "\n");
12837             linelen = 0;
12838             blank = 0;
12839         }
12840         if (blank) {
12841             fprintf(f, " ");
12842             linelen++;
12843         }
12844         fprintf(f, "%s", move_buffer);
12845         linelen += movelen;
12846
12847         /* [AS] Add PV info if present */
12848         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12849             /* [HGM] add time */
12850             char buf[MSG_SIZ]; int seconds;
12851
12852             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12853
12854             if( seconds <= 0)
12855               buf[0] = 0;
12856             else
12857               if( seconds < 30 )
12858                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12859               else
12860                 {
12861                   seconds = (seconds + 4)/10; // round to full seconds
12862                   if( seconds < 60 )
12863                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12864                   else
12865                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12866                 }
12867
12868             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12869                       pvInfoList[i].score >= 0 ? "+" : "",
12870                       pvInfoList[i].score / 100.0,
12871                       pvInfoList[i].depth,
12872                       buf );
12873
12874             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12875
12876             /* Print score/depth */
12877             blank = linelen > 0 && movelen > 0;
12878             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12879                 fprintf(f, "\n");
12880                 linelen = 0;
12881                 blank = 0;
12882             }
12883             if (blank) {
12884                 fprintf(f, " ");
12885                 linelen++;
12886             }
12887             fprintf(f, "%s", move_buffer);
12888             linelen += movelen;
12889         }
12890
12891         i++;
12892     }
12893
12894     /* Start a new line */
12895     if (linelen > 0) fprintf(f, "\n");
12896
12897     /* Print comments after last move */
12898     if (commentList[i] != NULL) {
12899         fprintf(f, "%s\n", commentList[i]);
12900     }
12901
12902     /* Print result */
12903     if (gameInfo.resultDetails != NULL &&
12904         gameInfo.resultDetails[0] != NULLCHAR) {
12905         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12906                 PGNResult(gameInfo.result));
12907     } else {
12908         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12909     }
12910
12911     fclose(f);
12912     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12913     return TRUE;
12914 }
12915
12916 /* Save game in old style and close the file */
12917 int
12918 SaveGameOldStyle (FILE *f)
12919 {
12920     int i, offset;
12921     time_t tm;
12922
12923     tm = time((time_t *) NULL);
12924
12925     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12926     PrintOpponents(f);
12927
12928     if (backwardMostMove > 0 || startedFromSetupPosition) {
12929         fprintf(f, "\n[--------------\n");
12930         PrintPosition(f, backwardMostMove);
12931         fprintf(f, "--------------]\n");
12932     } else {
12933         fprintf(f, "\n");
12934     }
12935
12936     i = backwardMostMove;
12937     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12938
12939     while (i < forwardMostMove) {
12940         if (commentList[i] != NULL) {
12941             fprintf(f, "[%s]\n", commentList[i]);
12942         }
12943
12944         if ((i % 2) == 1) {
12945             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12946             i++;
12947         } else {
12948             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12949             i++;
12950             if (commentList[i] != NULL) {
12951                 fprintf(f, "\n");
12952                 continue;
12953             }
12954             if (i >= forwardMostMove) {
12955                 fprintf(f, "\n");
12956                 break;
12957             }
12958             fprintf(f, "%s\n", parseList[i]);
12959             i++;
12960         }
12961     }
12962
12963     if (commentList[i] != NULL) {
12964         fprintf(f, "[%s]\n", commentList[i]);
12965     }
12966
12967     /* This isn't really the old style, but it's close enough */
12968     if (gameInfo.resultDetails != NULL &&
12969         gameInfo.resultDetails[0] != NULLCHAR) {
12970         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12971                 gameInfo.resultDetails);
12972     } else {
12973         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12974     }
12975
12976     fclose(f);
12977     return TRUE;
12978 }
12979
12980 /* Save the current game to open file f and close the file */
12981 int
12982 SaveGame (FILE *f, int dummy, char *dummy2)
12983 {
12984     if (gameMode == EditPosition) EditPositionDone(TRUE);
12985     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12986     if (appData.oldSaveStyle)
12987       return SaveGameOldStyle(f);
12988     else
12989       return SaveGamePGN(f);
12990 }
12991
12992 /* Save the current position to the given file */
12993 int
12994 SavePositionToFile (char *filename)
12995 {
12996     FILE *f;
12997     char buf[MSG_SIZ];
12998
12999     if (strcmp(filename, "-") == 0) {
13000         return SavePosition(stdout, 0, NULL);
13001     } else {
13002         f = fopen(filename, "a");
13003         if (f == NULL) {
13004             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13005             DisplayError(buf, errno);
13006             return FALSE;
13007         } else {
13008             safeStrCpy(buf, lastMsg, MSG_SIZ);
13009             DisplayMessage(_("Waiting for access to save file"), "");
13010             flock(fileno(f), LOCK_EX); // [HGM] lock
13011             DisplayMessage(_("Saving position"), "");
13012             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13013             SavePosition(f, 0, NULL);
13014             DisplayMessage(buf, "");
13015             return TRUE;
13016         }
13017     }
13018 }
13019
13020 /* Save the current position to the given open file and close the file */
13021 int
13022 SavePosition (FILE *f, int dummy, char *dummy2)
13023 {
13024     time_t tm;
13025     char *fen;
13026
13027     if (gameMode == EditPosition) EditPositionDone(TRUE);
13028     if (appData.oldSaveStyle) {
13029         tm = time((time_t *) NULL);
13030
13031         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13032         PrintOpponents(f);
13033         fprintf(f, "[--------------\n");
13034         PrintPosition(f, currentMove);
13035         fprintf(f, "--------------]\n");
13036     } else {
13037         fen = PositionToFEN(currentMove, NULL);
13038         fprintf(f, "%s\n", fen);
13039         free(fen);
13040     }
13041     fclose(f);
13042     return TRUE;
13043 }
13044
13045 void
13046 ReloadCmailMsgEvent (int unregister)
13047 {
13048 #if !WIN32
13049     static char *inFilename = NULL;
13050     static char *outFilename;
13051     int i;
13052     struct stat inbuf, outbuf;
13053     int status;
13054
13055     /* Any registered moves are unregistered if unregister is set, */
13056     /* i.e. invoked by the signal handler */
13057     if (unregister) {
13058         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13059             cmailMoveRegistered[i] = FALSE;
13060             if (cmailCommentList[i] != NULL) {
13061                 free(cmailCommentList[i]);
13062                 cmailCommentList[i] = NULL;
13063             }
13064         }
13065         nCmailMovesRegistered = 0;
13066     }
13067
13068     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13069         cmailResult[i] = CMAIL_NOT_RESULT;
13070     }
13071     nCmailResults = 0;
13072
13073     if (inFilename == NULL) {
13074         /* Because the filenames are static they only get malloced once  */
13075         /* and they never get freed                                      */
13076         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13077         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13078
13079         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13080         sprintf(outFilename, "%s.out", appData.cmailGameName);
13081     }
13082
13083     status = stat(outFilename, &outbuf);
13084     if (status < 0) {
13085         cmailMailedMove = FALSE;
13086     } else {
13087         status = stat(inFilename, &inbuf);
13088         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13089     }
13090
13091     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13092        counts the games, notes how each one terminated, etc.
13093
13094        It would be nice to remove this kludge and instead gather all
13095        the information while building the game list.  (And to keep it
13096        in the game list nodes instead of having a bunch of fixed-size
13097        parallel arrays.)  Note this will require getting each game's
13098        termination from the PGN tags, as the game list builder does
13099        not process the game moves.  --mann
13100        */
13101     cmailMsgLoaded = TRUE;
13102     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13103
13104     /* Load first game in the file or popup game menu */
13105     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13106
13107 #endif /* !WIN32 */
13108     return;
13109 }
13110
13111 int
13112 RegisterMove ()
13113 {
13114     FILE *f;
13115     char string[MSG_SIZ];
13116
13117     if (   cmailMailedMove
13118         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13119         return TRUE;            /* Allow free viewing  */
13120     }
13121
13122     /* Unregister move to ensure that we don't leave RegisterMove        */
13123     /* with the move registered when the conditions for registering no   */
13124     /* longer hold                                                       */
13125     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13126         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13127         nCmailMovesRegistered --;
13128
13129         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13130           {
13131               free(cmailCommentList[lastLoadGameNumber - 1]);
13132               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13133           }
13134     }
13135
13136     if (cmailOldMove == -1) {
13137         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13138         return FALSE;
13139     }
13140
13141     if (currentMove > cmailOldMove + 1) {
13142         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13143         return FALSE;
13144     }
13145
13146     if (currentMove < cmailOldMove) {
13147         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13148         return FALSE;
13149     }
13150
13151     if (forwardMostMove > currentMove) {
13152         /* Silently truncate extra moves */
13153         TruncateGame();
13154     }
13155
13156     if (   (currentMove == cmailOldMove + 1)
13157         || (   (currentMove == cmailOldMove)
13158             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13159                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13160         if (gameInfo.result != GameUnfinished) {
13161             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13162         }
13163
13164         if (commentList[currentMove] != NULL) {
13165             cmailCommentList[lastLoadGameNumber - 1]
13166               = StrSave(commentList[currentMove]);
13167         }
13168         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13169
13170         if (appData.debugMode)
13171           fprintf(debugFP, "Saving %s for game %d\n",
13172                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13173
13174         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13175
13176         f = fopen(string, "w");
13177         if (appData.oldSaveStyle) {
13178             SaveGameOldStyle(f); /* also closes the file */
13179
13180             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13181             f = fopen(string, "w");
13182             SavePosition(f, 0, NULL); /* also closes the file */
13183         } else {
13184             fprintf(f, "{--------------\n");
13185             PrintPosition(f, currentMove);
13186             fprintf(f, "--------------}\n\n");
13187
13188             SaveGame(f, 0, NULL); /* also closes the file*/
13189         }
13190
13191         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13192         nCmailMovesRegistered ++;
13193     } else if (nCmailGames == 1) {
13194         DisplayError(_("You have not made a move yet"), 0);
13195         return FALSE;
13196     }
13197
13198     return TRUE;
13199 }
13200
13201 void
13202 MailMoveEvent ()
13203 {
13204 #if !WIN32
13205     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13206     FILE *commandOutput;
13207     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13208     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13209     int nBuffers;
13210     int i;
13211     int archived;
13212     char *arcDir;
13213
13214     if (! cmailMsgLoaded) {
13215         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13216         return;
13217     }
13218
13219     if (nCmailGames == nCmailResults) {
13220         DisplayError(_("No unfinished games"), 0);
13221         return;
13222     }
13223
13224 #if CMAIL_PROHIBIT_REMAIL
13225     if (cmailMailedMove) {
13226       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);
13227         DisplayError(msg, 0);
13228         return;
13229     }
13230 #endif
13231
13232     if (! (cmailMailedMove || RegisterMove())) return;
13233
13234     if (   cmailMailedMove
13235         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13236       snprintf(string, MSG_SIZ, partCommandString,
13237                appData.debugMode ? " -v" : "", appData.cmailGameName);
13238         commandOutput = popen(string, "r");
13239
13240         if (commandOutput == NULL) {
13241             DisplayError(_("Failed to invoke cmail"), 0);
13242         } else {
13243             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13244                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13245             }
13246             if (nBuffers > 1) {
13247                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13248                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13249                 nBytes = MSG_SIZ - 1;
13250             } else {
13251                 (void) memcpy(msg, buffer, nBytes);
13252             }
13253             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13254
13255             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13256                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13257
13258                 archived = TRUE;
13259                 for (i = 0; i < nCmailGames; i ++) {
13260                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13261                         archived = FALSE;
13262                     }
13263                 }
13264                 if (   archived
13265                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13266                         != NULL)) {
13267                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13268                            arcDir,
13269                            appData.cmailGameName,
13270                            gameInfo.date);
13271                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13272                     cmailMsgLoaded = FALSE;
13273                 }
13274             }
13275
13276             DisplayInformation(msg);
13277             pclose(commandOutput);
13278         }
13279     } else {
13280         if ((*cmailMsg) != '\0') {
13281             DisplayInformation(cmailMsg);
13282         }
13283     }
13284
13285     return;
13286 #endif /* !WIN32 */
13287 }
13288
13289 char *
13290 CmailMsg ()
13291 {
13292 #if WIN32
13293     return NULL;
13294 #else
13295     int  prependComma = 0;
13296     char number[5];
13297     char string[MSG_SIZ];       /* Space for game-list */
13298     int  i;
13299
13300     if (!cmailMsgLoaded) return "";
13301
13302     if (cmailMailedMove) {
13303       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13304     } else {
13305         /* Create a list of games left */
13306       snprintf(string, MSG_SIZ, "[");
13307         for (i = 0; i < nCmailGames; i ++) {
13308             if (! (   cmailMoveRegistered[i]
13309                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13310                 if (prependComma) {
13311                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13312                 } else {
13313                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13314                     prependComma = 1;
13315                 }
13316
13317                 strcat(string, number);
13318             }
13319         }
13320         strcat(string, "]");
13321
13322         if (nCmailMovesRegistered + nCmailResults == 0) {
13323             switch (nCmailGames) {
13324               case 1:
13325                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13326                 break;
13327
13328               case 2:
13329                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13330                 break;
13331
13332               default:
13333                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13334                          nCmailGames);
13335                 break;
13336             }
13337         } else {
13338             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13339               case 1:
13340                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13341                          string);
13342                 break;
13343
13344               case 0:
13345                 if (nCmailResults == nCmailGames) {
13346                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13347                 } else {
13348                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13349                 }
13350                 break;
13351
13352               default:
13353                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13354                          string);
13355             }
13356         }
13357     }
13358     return cmailMsg;
13359 #endif /* WIN32 */
13360 }
13361
13362 void
13363 ResetGameEvent ()
13364 {
13365     if (gameMode == Training)
13366       SetTrainingModeOff();
13367
13368     Reset(TRUE, TRUE);
13369     cmailMsgLoaded = FALSE;
13370     if (appData.icsActive) {
13371       SendToICS(ics_prefix);
13372       SendToICS("refresh\n");
13373     }
13374 }
13375
13376 void
13377 ExitEvent (int status)
13378 {
13379     exiting++;
13380     if (exiting > 2) {
13381       /* Give up on clean exit */
13382       exit(status);
13383     }
13384     if (exiting > 1) {
13385       /* Keep trying for clean exit */
13386       return;
13387     }
13388
13389     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13390
13391     if (telnetISR != NULL) {
13392       RemoveInputSource(telnetISR);
13393     }
13394     if (icsPR != NoProc) {
13395       DestroyChildProcess(icsPR, TRUE);
13396     }
13397
13398     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13399     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13400
13401     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13402     /* make sure this other one finishes before killing it!                  */
13403     if(endingGame) { int count = 0;
13404         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13405         while(endingGame && count++ < 10) DoSleep(1);
13406         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13407     }
13408
13409     /* Kill off chess programs */
13410     if (first.pr != NoProc) {
13411         ExitAnalyzeMode();
13412
13413         DoSleep( appData.delayBeforeQuit );
13414         SendToProgram("quit\n", &first);
13415         DoSleep( appData.delayAfterQuit );
13416         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13417     }
13418     if (second.pr != NoProc) {
13419         DoSleep( appData.delayBeforeQuit );
13420         SendToProgram("quit\n", &second);
13421         DoSleep( appData.delayAfterQuit );
13422         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13423     }
13424     if (first.isr != NULL) {
13425         RemoveInputSource(first.isr);
13426     }
13427     if (second.isr != NULL) {
13428         RemoveInputSource(second.isr);
13429     }
13430
13431     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13432     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13433
13434     ShutDownFrontEnd();
13435     exit(status);
13436 }
13437
13438 void
13439 PauseEngine (ChessProgramState *cps)
13440 {
13441     SendToProgram("pause\n", cps);
13442     cps->pause = 2;
13443 }
13444
13445 void
13446 UnPauseEngine (ChessProgramState *cps)
13447 {
13448     SendToProgram("resume\n", cps);
13449     cps->pause = 1;
13450 }
13451
13452 void
13453 PauseEvent ()
13454 {
13455     if (appData.debugMode)
13456         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13457     if (pausing) {
13458         pausing = FALSE;
13459         ModeHighlight();
13460         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13461             StartClocks();
13462             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13463                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13464                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13465             }
13466             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13467             HandleMachineMove(stashedInputMove, stalledEngine);
13468             stalledEngine = NULL;
13469             return;
13470         }
13471         if (gameMode == MachinePlaysWhite ||
13472             gameMode == TwoMachinesPlay   ||
13473             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13474             if(first.pause)  UnPauseEngine(&first);
13475             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13476             if(second.pause) UnPauseEngine(&second);
13477             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13478             StartClocks();
13479         } else {
13480             DisplayBothClocks();
13481         }
13482         if (gameMode == PlayFromGameFile) {
13483             if (appData.timeDelay >= 0)
13484                 AutoPlayGameLoop();
13485         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13486             Reset(FALSE, TRUE);
13487             SendToICS(ics_prefix);
13488             SendToICS("refresh\n");
13489         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13490             ForwardInner(forwardMostMove);
13491         }
13492         pauseExamInvalid = FALSE;
13493     } else {
13494         switch (gameMode) {
13495           default:
13496             return;
13497           case IcsExamining:
13498             pauseExamForwardMostMove = forwardMostMove;
13499             pauseExamInvalid = FALSE;
13500             /* fall through */
13501           case IcsObserving:
13502           case IcsPlayingWhite:
13503           case IcsPlayingBlack:
13504             pausing = TRUE;
13505             ModeHighlight();
13506             return;
13507           case PlayFromGameFile:
13508             (void) StopLoadGameTimer();
13509             pausing = TRUE;
13510             ModeHighlight();
13511             break;
13512           case BeginningOfGame:
13513             if (appData.icsActive) return;
13514             /* else fall through */
13515           case MachinePlaysWhite:
13516           case MachinePlaysBlack:
13517           case TwoMachinesPlay:
13518             if (forwardMostMove == 0)
13519               return;           /* don't pause if no one has moved */
13520             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13521                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13522                 if(onMove->pause) {           // thinking engine can be paused
13523                     PauseEngine(onMove);      // do it
13524                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13525                         PauseEngine(onMove->other);
13526                     else
13527                         SendToProgram("easy\n", onMove->other);
13528                     StopClocks();
13529                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13530             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13531                 if(first.pause) {
13532                     PauseEngine(&first);
13533                     StopClocks();
13534                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13535             } else { // human on move, pause pondering by either method
13536                 if(first.pause)
13537                     PauseEngine(&first);
13538                 else if(appData.ponderNextMove)
13539                     SendToProgram("easy\n", &first);
13540                 StopClocks();
13541             }
13542             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13543           case AnalyzeMode:
13544             pausing = TRUE;
13545             ModeHighlight();
13546             break;
13547         }
13548     }
13549 }
13550
13551 void
13552 EditCommentEvent ()
13553 {
13554     char title[MSG_SIZ];
13555
13556     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13557       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13558     } else {
13559       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13560                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13561                parseList[currentMove - 1]);
13562     }
13563
13564     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13565 }
13566
13567
13568 void
13569 EditTagsEvent ()
13570 {
13571     char *tags = PGNTags(&gameInfo);
13572     bookUp = FALSE;
13573     EditTagsPopUp(tags, NULL);
13574     free(tags);
13575 }
13576
13577 void
13578 ToggleSecond ()
13579 {
13580   if(second.analyzing) {
13581     SendToProgram("exit\n", &second);
13582     second.analyzing = FALSE;
13583   } else {
13584     if (second.pr == NoProc) StartChessProgram(&second);
13585     InitChessProgram(&second, FALSE);
13586     FeedMovesToProgram(&second, currentMove);
13587
13588     SendToProgram("analyze\n", &second);
13589     second.analyzing = TRUE;
13590   }
13591 }
13592
13593 /* Toggle ShowThinking */
13594 void
13595 ToggleShowThinking()
13596 {
13597   appData.showThinking = !appData.showThinking;
13598   ShowThinkingEvent();
13599 }
13600
13601 int
13602 AnalyzeModeEvent ()
13603 {
13604     char buf[MSG_SIZ];
13605
13606     if (!first.analysisSupport) {
13607       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13608       DisplayError(buf, 0);
13609       return 0;
13610     }
13611     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13612     if (appData.icsActive) {
13613         if (gameMode != IcsObserving) {
13614           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13615             DisplayError(buf, 0);
13616             /* secure check */
13617             if (appData.icsEngineAnalyze) {
13618                 if (appData.debugMode)
13619                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13620                 ExitAnalyzeMode();
13621                 ModeHighlight();
13622             }
13623             return 0;
13624         }
13625         /* if enable, user wants to disable icsEngineAnalyze */
13626         if (appData.icsEngineAnalyze) {
13627                 ExitAnalyzeMode();
13628                 ModeHighlight();
13629                 return 0;
13630         }
13631         appData.icsEngineAnalyze = TRUE;
13632         if (appData.debugMode)
13633             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13634     }
13635
13636     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13637     if (appData.noChessProgram || gameMode == AnalyzeMode)
13638       return 0;
13639
13640     if (gameMode != AnalyzeFile) {
13641         if (!appData.icsEngineAnalyze) {
13642                EditGameEvent();
13643                if (gameMode != EditGame) return 0;
13644         }
13645         if (!appData.showThinking) ToggleShowThinking();
13646         ResurrectChessProgram();
13647         SendToProgram("analyze\n", &first);
13648         first.analyzing = TRUE;
13649         /*first.maybeThinking = TRUE;*/
13650         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13651         EngineOutputPopUp();
13652     }
13653     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13654     pausing = FALSE;
13655     ModeHighlight();
13656     SetGameInfo();
13657
13658     StartAnalysisClock();
13659     GetTimeMark(&lastNodeCountTime);
13660     lastNodeCount = 0;
13661     return 1;
13662 }
13663
13664 void
13665 AnalyzeFileEvent ()
13666 {
13667     if (appData.noChessProgram || gameMode == AnalyzeFile)
13668       return;
13669
13670     if (!first.analysisSupport) {
13671       char buf[MSG_SIZ];
13672       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13673       DisplayError(buf, 0);
13674       return;
13675     }
13676
13677     if (gameMode != AnalyzeMode) {
13678         keepInfo = 1; // mere annotating should not alter PGN tags
13679         EditGameEvent();
13680         keepInfo = 0;
13681         if (gameMode != EditGame) return;
13682         if (!appData.showThinking) ToggleShowThinking();
13683         ResurrectChessProgram();
13684         SendToProgram("analyze\n", &first);
13685         first.analyzing = TRUE;
13686         /*first.maybeThinking = TRUE;*/
13687         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13688         EngineOutputPopUp();
13689     }
13690     gameMode = AnalyzeFile;
13691     pausing = FALSE;
13692     ModeHighlight();
13693
13694     StartAnalysisClock();
13695     GetTimeMark(&lastNodeCountTime);
13696     lastNodeCount = 0;
13697     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13698     AnalysisPeriodicEvent(1);
13699 }
13700
13701 void
13702 MachineWhiteEvent ()
13703 {
13704     char buf[MSG_SIZ];
13705     char *bookHit = NULL;
13706
13707     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13708       return;
13709
13710
13711     if (gameMode == PlayFromGameFile ||
13712         gameMode == TwoMachinesPlay  ||
13713         gameMode == Training         ||
13714         gameMode == AnalyzeMode      ||
13715         gameMode == EndOfGame)
13716         EditGameEvent();
13717
13718     if (gameMode == EditPosition)
13719         EditPositionDone(TRUE);
13720
13721     if (!WhiteOnMove(currentMove)) {
13722         DisplayError(_("It is not White's turn"), 0);
13723         return;
13724     }
13725
13726     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13727       ExitAnalyzeMode();
13728
13729     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13730         gameMode == AnalyzeFile)
13731         TruncateGame();
13732
13733     ResurrectChessProgram();    /* in case it isn't running */
13734     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13735         gameMode = MachinePlaysWhite;
13736         ResetClocks();
13737     } else
13738     gameMode = MachinePlaysWhite;
13739     pausing = FALSE;
13740     ModeHighlight();
13741     SetGameInfo();
13742     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13743     DisplayTitle(buf);
13744     if (first.sendName) {
13745       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13746       SendToProgram(buf, &first);
13747     }
13748     if (first.sendTime) {
13749       if (first.useColors) {
13750         SendToProgram("black\n", &first); /*gnu kludge*/
13751       }
13752       SendTimeRemaining(&first, TRUE);
13753     }
13754     if (first.useColors) {
13755       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13756     }
13757     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13758     SetMachineThinkingEnables();
13759     first.maybeThinking = TRUE;
13760     StartClocks();
13761     firstMove = FALSE;
13762
13763     if (appData.autoFlipView && !flipView) {
13764       flipView = !flipView;
13765       DrawPosition(FALSE, NULL);
13766       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13767     }
13768
13769     if(bookHit) { // [HGM] book: simulate book reply
13770         static char bookMove[MSG_SIZ]; // a bit generous?
13771
13772         programStats.nodes = programStats.depth = programStats.time =
13773         programStats.score = programStats.got_only_move = 0;
13774         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13775
13776         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13777         strcat(bookMove, bookHit);
13778         HandleMachineMove(bookMove, &first);
13779     }
13780 }
13781
13782 void
13783 MachineBlackEvent ()
13784 {
13785   char buf[MSG_SIZ];
13786   char *bookHit = NULL;
13787
13788     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13789         return;
13790
13791
13792     if (gameMode == PlayFromGameFile ||
13793         gameMode == TwoMachinesPlay  ||
13794         gameMode == Training         ||
13795         gameMode == AnalyzeMode      ||
13796         gameMode == EndOfGame)
13797         EditGameEvent();
13798
13799     if (gameMode == EditPosition)
13800         EditPositionDone(TRUE);
13801
13802     if (WhiteOnMove(currentMove)) {
13803         DisplayError(_("It is not Black's turn"), 0);
13804         return;
13805     }
13806
13807     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13808       ExitAnalyzeMode();
13809
13810     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13811         gameMode == AnalyzeFile)
13812         TruncateGame();
13813
13814     ResurrectChessProgram();    /* in case it isn't running */
13815     gameMode = MachinePlaysBlack;
13816     pausing = FALSE;
13817     ModeHighlight();
13818     SetGameInfo();
13819     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13820     DisplayTitle(buf);
13821     if (first.sendName) {
13822       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13823       SendToProgram(buf, &first);
13824     }
13825     if (first.sendTime) {
13826       if (first.useColors) {
13827         SendToProgram("white\n", &first); /*gnu kludge*/
13828       }
13829       SendTimeRemaining(&first, FALSE);
13830     }
13831     if (first.useColors) {
13832       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13833     }
13834     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13835     SetMachineThinkingEnables();
13836     first.maybeThinking = TRUE;
13837     StartClocks();
13838
13839     if (appData.autoFlipView && flipView) {
13840       flipView = !flipView;
13841       DrawPosition(FALSE, NULL);
13842       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13843     }
13844     if(bookHit) { // [HGM] book: simulate book reply
13845         static char bookMove[MSG_SIZ]; // a bit generous?
13846
13847         programStats.nodes = programStats.depth = programStats.time =
13848         programStats.score = programStats.got_only_move = 0;
13849         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13850
13851         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13852         strcat(bookMove, bookHit);
13853         HandleMachineMove(bookMove, &first);
13854     }
13855 }
13856
13857
13858 void
13859 DisplayTwoMachinesTitle ()
13860 {
13861     char buf[MSG_SIZ];
13862     if (appData.matchGames > 0) {
13863         if(appData.tourneyFile[0]) {
13864           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13865                    gameInfo.white, _("vs."), gameInfo.black,
13866                    nextGame+1, appData.matchGames+1,
13867                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13868         } else
13869         if (first.twoMachinesColor[0] == 'w') {
13870           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13871                    gameInfo.white, _("vs."),  gameInfo.black,
13872                    first.matchWins, second.matchWins,
13873                    matchGame - 1 - (first.matchWins + second.matchWins));
13874         } else {
13875           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13876                    gameInfo.white, _("vs."), gameInfo.black,
13877                    second.matchWins, first.matchWins,
13878                    matchGame - 1 - (first.matchWins + second.matchWins));
13879         }
13880     } else {
13881       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13882     }
13883     DisplayTitle(buf);
13884 }
13885
13886 void
13887 SettingsMenuIfReady ()
13888 {
13889   if (second.lastPing != second.lastPong) {
13890     DisplayMessage("", _("Waiting for second chess program"));
13891     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13892     return;
13893   }
13894   ThawUI();
13895   DisplayMessage("", "");
13896   SettingsPopUp(&second);
13897 }
13898
13899 int
13900 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13901 {
13902     char buf[MSG_SIZ];
13903     if (cps->pr == NoProc) {
13904         StartChessProgram(cps);
13905         if (cps->protocolVersion == 1) {
13906           retry();
13907         } else {
13908           /* kludge: allow timeout for initial "feature" command */
13909           FreezeUI();
13910           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13911           DisplayMessage("", buf);
13912           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13913         }
13914         return 1;
13915     }
13916     return 0;
13917 }
13918
13919 void
13920 TwoMachinesEvent P((void))
13921 {
13922     int i;
13923     char buf[MSG_SIZ];
13924     ChessProgramState *onmove;
13925     char *bookHit = NULL;
13926     static int stalling = 0;
13927     TimeMark now;
13928     long wait;
13929
13930     if (appData.noChessProgram) return;
13931
13932     switch (gameMode) {
13933       case TwoMachinesPlay:
13934         return;
13935       case MachinePlaysWhite:
13936       case MachinePlaysBlack:
13937         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13938             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13939             return;
13940         }
13941         /* fall through */
13942       case BeginningOfGame:
13943       case PlayFromGameFile:
13944       case EndOfGame:
13945         EditGameEvent();
13946         if (gameMode != EditGame) return;
13947         break;
13948       case EditPosition:
13949         EditPositionDone(TRUE);
13950         break;
13951       case AnalyzeMode:
13952       case AnalyzeFile:
13953         ExitAnalyzeMode();
13954         break;
13955       case EditGame:
13956       default:
13957         break;
13958     }
13959
13960 //    forwardMostMove = currentMove;
13961     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13962
13963     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13964
13965     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13966     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13967       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13968       return;
13969     }
13970
13971     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13972         DisplayError("second engine does not play this", 0);
13973         return;
13974     }
13975
13976     if(!stalling) {
13977       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13978       SendToProgram("force\n", &second);
13979       stalling = 1;
13980       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13981       return;
13982     }
13983     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13984     if(appData.matchPause>10000 || appData.matchPause<10)
13985                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13986     wait = SubtractTimeMarks(&now, &pauseStart);
13987     if(wait < appData.matchPause) {
13988         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13989         return;
13990     }
13991     // we are now committed to starting the game
13992     stalling = 0;
13993     DisplayMessage("", "");
13994     if (startedFromSetupPosition) {
13995         SendBoard(&second, backwardMostMove);
13996     if (appData.debugMode) {
13997         fprintf(debugFP, "Two Machines\n");
13998     }
13999     }
14000     for (i = backwardMostMove; i < forwardMostMove; i++) {
14001         SendMoveToProgram(i, &second);
14002     }
14003
14004     gameMode = TwoMachinesPlay;
14005     pausing = FALSE;
14006     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14007     SetGameInfo();
14008     DisplayTwoMachinesTitle();
14009     firstMove = TRUE;
14010     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14011         onmove = &first;
14012     } else {
14013         onmove = &second;
14014     }
14015     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14016     SendToProgram(first.computerString, &first);
14017     if (first.sendName) {
14018       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14019       SendToProgram(buf, &first);
14020     }
14021     SendToProgram(second.computerString, &second);
14022     if (second.sendName) {
14023       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14024       SendToProgram(buf, &second);
14025     }
14026
14027     ResetClocks();
14028     if (!first.sendTime || !second.sendTime) {
14029         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14030         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14031     }
14032     if (onmove->sendTime) {
14033       if (onmove->useColors) {
14034         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14035       }
14036       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14037     }
14038     if (onmove->useColors) {
14039       SendToProgram(onmove->twoMachinesColor, onmove);
14040     }
14041     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14042 //    SendToProgram("go\n", onmove);
14043     onmove->maybeThinking = TRUE;
14044     SetMachineThinkingEnables();
14045
14046     StartClocks();
14047
14048     if(bookHit) { // [HGM] book: simulate book reply
14049         static char bookMove[MSG_SIZ]; // a bit generous?
14050
14051         programStats.nodes = programStats.depth = programStats.time =
14052         programStats.score = programStats.got_only_move = 0;
14053         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14054
14055         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14056         strcat(bookMove, bookHit);
14057         savedMessage = bookMove; // args for deferred call
14058         savedState = onmove;
14059         ScheduleDelayedEvent(DeferredBookMove, 1);
14060     }
14061 }
14062
14063 void
14064 TrainingEvent ()
14065 {
14066     if (gameMode == Training) {
14067       SetTrainingModeOff();
14068       gameMode = PlayFromGameFile;
14069       DisplayMessage("", _("Training mode off"));
14070     } else {
14071       gameMode = Training;
14072       animateTraining = appData.animate;
14073
14074       /* make sure we are not already at the end of the game */
14075       if (currentMove < forwardMostMove) {
14076         SetTrainingModeOn();
14077         DisplayMessage("", _("Training mode on"));
14078       } else {
14079         gameMode = PlayFromGameFile;
14080         DisplayError(_("Already at end of game"), 0);
14081       }
14082     }
14083     ModeHighlight();
14084 }
14085
14086 void
14087 IcsClientEvent ()
14088 {
14089     if (!appData.icsActive) return;
14090     switch (gameMode) {
14091       case IcsPlayingWhite:
14092       case IcsPlayingBlack:
14093       case IcsObserving:
14094       case IcsIdle:
14095       case BeginningOfGame:
14096       case IcsExamining:
14097         return;
14098
14099       case EditGame:
14100         break;
14101
14102       case EditPosition:
14103         EditPositionDone(TRUE);
14104         break;
14105
14106       case AnalyzeMode:
14107       case AnalyzeFile:
14108         ExitAnalyzeMode();
14109         break;
14110
14111       default:
14112         EditGameEvent();
14113         break;
14114     }
14115
14116     gameMode = IcsIdle;
14117     ModeHighlight();
14118     return;
14119 }
14120
14121 void
14122 EditGameEvent ()
14123 {
14124     int i;
14125
14126     switch (gameMode) {
14127       case Training:
14128         SetTrainingModeOff();
14129         break;
14130       case MachinePlaysWhite:
14131       case MachinePlaysBlack:
14132       case BeginningOfGame:
14133         SendToProgram("force\n", &first);
14134         SetUserThinkingEnables();
14135         break;
14136       case PlayFromGameFile:
14137         (void) StopLoadGameTimer();
14138         if (gameFileFP != NULL) {
14139             gameFileFP = NULL;
14140         }
14141         break;
14142       case EditPosition:
14143         EditPositionDone(TRUE);
14144         break;
14145       case AnalyzeMode:
14146       case AnalyzeFile:
14147         ExitAnalyzeMode();
14148         SendToProgram("force\n", &first);
14149         break;
14150       case TwoMachinesPlay:
14151         GameEnds(EndOfFile, NULL, GE_PLAYER);
14152         ResurrectChessProgram();
14153         SetUserThinkingEnables();
14154         break;
14155       case EndOfGame:
14156         ResurrectChessProgram();
14157         break;
14158       case IcsPlayingBlack:
14159       case IcsPlayingWhite:
14160         DisplayError(_("Warning: You are still playing a game"), 0);
14161         break;
14162       case IcsObserving:
14163         DisplayError(_("Warning: You are still observing a game"), 0);
14164         break;
14165       case IcsExamining:
14166         DisplayError(_("Warning: You are still examining a game"), 0);
14167         break;
14168       case IcsIdle:
14169         break;
14170       case EditGame:
14171       default:
14172         return;
14173     }
14174
14175     pausing = FALSE;
14176     StopClocks();
14177     first.offeredDraw = second.offeredDraw = 0;
14178
14179     if (gameMode == PlayFromGameFile) {
14180         whiteTimeRemaining = timeRemaining[0][currentMove];
14181         blackTimeRemaining = timeRemaining[1][currentMove];
14182         DisplayTitle("");
14183     }
14184
14185     if (gameMode == MachinePlaysWhite ||
14186         gameMode == MachinePlaysBlack ||
14187         gameMode == TwoMachinesPlay ||
14188         gameMode == EndOfGame) {
14189         i = forwardMostMove;
14190         while (i > currentMove) {
14191             SendToProgram("undo\n", &first);
14192             i--;
14193         }
14194         if(!adjustedClock) {
14195         whiteTimeRemaining = timeRemaining[0][currentMove];
14196         blackTimeRemaining = timeRemaining[1][currentMove];
14197         DisplayBothClocks();
14198         }
14199         if (whiteFlag || blackFlag) {
14200             whiteFlag = blackFlag = 0;
14201         }
14202         DisplayTitle("");
14203     }
14204
14205     gameMode = EditGame;
14206     ModeHighlight();
14207     SetGameInfo();
14208 }
14209
14210
14211 void
14212 EditPositionEvent ()
14213 {
14214     if (gameMode == EditPosition) {
14215         EditGameEvent();
14216         return;
14217     }
14218
14219     EditGameEvent();
14220     if (gameMode != EditGame) return;
14221
14222     gameMode = EditPosition;
14223     ModeHighlight();
14224     SetGameInfo();
14225     if (currentMove > 0)
14226       CopyBoard(boards[0], boards[currentMove]);
14227
14228     blackPlaysFirst = !WhiteOnMove(currentMove);
14229     ResetClocks();
14230     currentMove = forwardMostMove = backwardMostMove = 0;
14231     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14232     DisplayMove(-1);
14233     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14234 }
14235
14236 void
14237 ExitAnalyzeMode ()
14238 {
14239     /* [DM] icsEngineAnalyze - possible call from other functions */
14240     if (appData.icsEngineAnalyze) {
14241         appData.icsEngineAnalyze = FALSE;
14242
14243         DisplayMessage("",_("Close ICS engine analyze..."));
14244     }
14245     if (first.analysisSupport && first.analyzing) {
14246       SendToBoth("exit\n");
14247       first.analyzing = second.analyzing = FALSE;
14248     }
14249     thinkOutput[0] = NULLCHAR;
14250 }
14251
14252 void
14253 EditPositionDone (Boolean fakeRights)
14254 {
14255     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14256
14257     startedFromSetupPosition = TRUE;
14258     InitChessProgram(&first, FALSE);
14259     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14260       boards[0][EP_STATUS] = EP_NONE;
14261       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14262       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14263         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14264         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14265       } else boards[0][CASTLING][2] = NoRights;
14266       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14267         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14268         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14269       } else boards[0][CASTLING][5] = NoRights;
14270       if(gameInfo.variant == VariantSChess) {
14271         int i;
14272         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14273           boards[0][VIRGIN][i] = 0;
14274           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14275           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14276         }
14277       }
14278     }
14279     SendToProgram("force\n", &first);
14280     if (blackPlaysFirst) {
14281         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14282         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14283         currentMove = forwardMostMove = backwardMostMove = 1;
14284         CopyBoard(boards[1], boards[0]);
14285     } else {
14286         currentMove = forwardMostMove = backwardMostMove = 0;
14287     }
14288     SendBoard(&first, forwardMostMove);
14289     if (appData.debugMode) {
14290         fprintf(debugFP, "EditPosDone\n");
14291     }
14292     DisplayTitle("");
14293     DisplayMessage("", "");
14294     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14295     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14296     gameMode = EditGame;
14297     ModeHighlight();
14298     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14299     ClearHighlights(); /* [AS] */
14300 }
14301
14302 /* Pause for `ms' milliseconds */
14303 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14304 void
14305 TimeDelay (long ms)
14306 {
14307     TimeMark m1, m2;
14308
14309     GetTimeMark(&m1);
14310     do {
14311         GetTimeMark(&m2);
14312     } while (SubtractTimeMarks(&m2, &m1) < ms);
14313 }
14314
14315 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14316 void
14317 SendMultiLineToICS (char *buf)
14318 {
14319     char temp[MSG_SIZ+1], *p;
14320     int len;
14321
14322     len = strlen(buf);
14323     if (len > MSG_SIZ)
14324       len = MSG_SIZ;
14325
14326     strncpy(temp, buf, len);
14327     temp[len] = 0;
14328
14329     p = temp;
14330     while (*p) {
14331         if (*p == '\n' || *p == '\r')
14332           *p = ' ';
14333         ++p;
14334     }
14335
14336     strcat(temp, "\n");
14337     SendToICS(temp);
14338     SendToPlayer(temp, strlen(temp));
14339 }
14340
14341 void
14342 SetWhiteToPlayEvent ()
14343 {
14344     if (gameMode == EditPosition) {
14345         blackPlaysFirst = FALSE;
14346         DisplayBothClocks();    /* works because currentMove is 0 */
14347     } else if (gameMode == IcsExamining) {
14348         SendToICS(ics_prefix);
14349         SendToICS("tomove white\n");
14350     }
14351 }
14352
14353 void
14354 SetBlackToPlayEvent ()
14355 {
14356     if (gameMode == EditPosition) {
14357         blackPlaysFirst = TRUE;
14358         currentMove = 1;        /* kludge */
14359         DisplayBothClocks();
14360         currentMove = 0;
14361     } else if (gameMode == IcsExamining) {
14362         SendToICS(ics_prefix);
14363         SendToICS("tomove black\n");
14364     }
14365 }
14366
14367 void
14368 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14369 {
14370     char buf[MSG_SIZ];
14371     ChessSquare piece = boards[0][y][x];
14372
14373     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14374
14375     switch (selection) {
14376       case ClearBoard:
14377         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14378             SendToICS(ics_prefix);
14379             SendToICS("bsetup clear\n");
14380         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14381             SendToICS(ics_prefix);
14382             SendToICS("clearboard\n");
14383         } else {
14384             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14385                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14386                 for (y = 0; y < BOARD_HEIGHT; y++) {
14387                     if (gameMode == IcsExamining) {
14388                         if (boards[currentMove][y][x] != EmptySquare) {
14389                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14390                                     AAA + x, ONE + y);
14391                             SendToICS(buf);
14392                         }
14393                     } else {
14394                         boards[0][y][x] = p;
14395                     }
14396                 }
14397             }
14398         }
14399         if (gameMode == EditPosition) {
14400             DrawPosition(FALSE, boards[0]);
14401         }
14402         break;
14403
14404       case WhitePlay:
14405         SetWhiteToPlayEvent();
14406         break;
14407
14408       case BlackPlay:
14409         SetBlackToPlayEvent();
14410         break;
14411
14412       case EmptySquare:
14413         if (gameMode == IcsExamining) {
14414             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14415             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14416             SendToICS(buf);
14417         } else {
14418             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14419                 if(x == BOARD_LEFT-2) {
14420                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14421                     boards[0][y][1] = 0;
14422                 } else
14423                 if(x == BOARD_RGHT+1) {
14424                     if(y >= gameInfo.holdingsSize) break;
14425                     boards[0][y][BOARD_WIDTH-2] = 0;
14426                 } else break;
14427             }
14428             boards[0][y][x] = EmptySquare;
14429             DrawPosition(FALSE, boards[0]);
14430         }
14431         break;
14432
14433       case PromotePiece:
14434         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14435            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14436             selection = (ChessSquare) (PROMOTED piece);
14437         } else if(piece == EmptySquare) selection = WhiteSilver;
14438         else selection = (ChessSquare)((int)piece - 1);
14439         goto defaultlabel;
14440
14441       case DemotePiece:
14442         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14443            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14444             selection = (ChessSquare) (DEMOTED piece);
14445         } else if(piece == EmptySquare) selection = BlackSilver;
14446         else selection = (ChessSquare)((int)piece + 1);
14447         goto defaultlabel;
14448
14449       case WhiteQueen:
14450       case BlackQueen:
14451         if(gameInfo.variant == VariantShatranj ||
14452            gameInfo.variant == VariantXiangqi  ||
14453            gameInfo.variant == VariantCourier  ||
14454            gameInfo.variant == VariantMakruk     )
14455             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14456         goto defaultlabel;
14457
14458       case WhiteKing:
14459       case BlackKing:
14460         if(gameInfo.variant == VariantXiangqi)
14461             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14462         if(gameInfo.variant == VariantKnightmate)
14463             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14464       default:
14465         defaultlabel:
14466         if (gameMode == IcsExamining) {
14467             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14468             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14469                      PieceToChar(selection), AAA + x, ONE + y);
14470             SendToICS(buf);
14471         } else {
14472             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14473                 int n;
14474                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14475                     n = PieceToNumber(selection - BlackPawn);
14476                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14477                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14478                     boards[0][BOARD_HEIGHT-1-n][1]++;
14479                 } else
14480                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14481                     n = PieceToNumber(selection);
14482                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14483                     boards[0][n][BOARD_WIDTH-1] = selection;
14484                     boards[0][n][BOARD_WIDTH-2]++;
14485                 }
14486             } else
14487             boards[0][y][x] = selection;
14488             DrawPosition(TRUE, boards[0]);
14489             ClearHighlights();
14490             fromX = fromY = -1;
14491         }
14492         break;
14493     }
14494 }
14495
14496
14497 void
14498 DropMenuEvent (ChessSquare selection, int x, int y)
14499 {
14500     ChessMove moveType;
14501
14502     switch (gameMode) {
14503       case IcsPlayingWhite:
14504       case MachinePlaysBlack:
14505         if (!WhiteOnMove(currentMove)) {
14506             DisplayMoveError(_("It is Black's turn"));
14507             return;
14508         }
14509         moveType = WhiteDrop;
14510         break;
14511       case IcsPlayingBlack:
14512       case MachinePlaysWhite:
14513         if (WhiteOnMove(currentMove)) {
14514             DisplayMoveError(_("It is White's turn"));
14515             return;
14516         }
14517         moveType = BlackDrop;
14518         break;
14519       case EditGame:
14520         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14521         break;
14522       default:
14523         return;
14524     }
14525
14526     if (moveType == BlackDrop && selection < BlackPawn) {
14527       selection = (ChessSquare) ((int) selection
14528                                  + (int) BlackPawn - (int) WhitePawn);
14529     }
14530     if (boards[currentMove][y][x] != EmptySquare) {
14531         DisplayMoveError(_("That square is occupied"));
14532         return;
14533     }
14534
14535     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14536 }
14537
14538 void
14539 AcceptEvent ()
14540 {
14541     /* Accept a pending offer of any kind from opponent */
14542
14543     if (appData.icsActive) {
14544         SendToICS(ics_prefix);
14545         SendToICS("accept\n");
14546     } else if (cmailMsgLoaded) {
14547         if (currentMove == cmailOldMove &&
14548             commentList[cmailOldMove] != NULL &&
14549             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14550                    "Black offers a draw" : "White offers a draw")) {
14551             TruncateGame();
14552             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14553             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14554         } else {
14555             DisplayError(_("There is no pending offer on this move"), 0);
14556             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14557         }
14558     } else {
14559         /* Not used for offers from chess program */
14560     }
14561 }
14562
14563 void
14564 DeclineEvent ()
14565 {
14566     /* Decline a pending offer of any kind from opponent */
14567
14568     if (appData.icsActive) {
14569         SendToICS(ics_prefix);
14570         SendToICS("decline\n");
14571     } else if (cmailMsgLoaded) {
14572         if (currentMove == cmailOldMove &&
14573             commentList[cmailOldMove] != NULL &&
14574             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14575                    "Black offers a draw" : "White offers a draw")) {
14576 #ifdef NOTDEF
14577             AppendComment(cmailOldMove, "Draw declined", TRUE);
14578             DisplayComment(cmailOldMove - 1, "Draw declined");
14579 #endif /*NOTDEF*/
14580         } else {
14581             DisplayError(_("There is no pending offer on this move"), 0);
14582         }
14583     } else {
14584         /* Not used for offers from chess program */
14585     }
14586 }
14587
14588 void
14589 RematchEvent ()
14590 {
14591     /* Issue ICS rematch command */
14592     if (appData.icsActive) {
14593         SendToICS(ics_prefix);
14594         SendToICS("rematch\n");
14595     }
14596 }
14597
14598 void
14599 CallFlagEvent ()
14600 {
14601     /* Call your opponent's flag (claim a win on time) */
14602     if (appData.icsActive) {
14603         SendToICS(ics_prefix);
14604         SendToICS("flag\n");
14605     } else {
14606         switch (gameMode) {
14607           default:
14608             return;
14609           case MachinePlaysWhite:
14610             if (whiteFlag) {
14611                 if (blackFlag)
14612                   GameEnds(GameIsDrawn, "Both players ran out of time",
14613                            GE_PLAYER);
14614                 else
14615                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14616             } else {
14617                 DisplayError(_("Your opponent is not out of time"), 0);
14618             }
14619             break;
14620           case MachinePlaysBlack:
14621             if (blackFlag) {
14622                 if (whiteFlag)
14623                   GameEnds(GameIsDrawn, "Both players ran out of time",
14624                            GE_PLAYER);
14625                 else
14626                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14627             } else {
14628                 DisplayError(_("Your opponent is not out of time"), 0);
14629             }
14630             break;
14631         }
14632     }
14633 }
14634
14635 void
14636 ClockClick (int which)
14637 {       // [HGM] code moved to back-end from winboard.c
14638         if(which) { // black clock
14639           if (gameMode == EditPosition || gameMode == IcsExamining) {
14640             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14641             SetBlackToPlayEvent();
14642           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14643           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14644           } else if (shiftKey) {
14645             AdjustClock(which, -1);
14646           } else if (gameMode == IcsPlayingWhite ||
14647                      gameMode == MachinePlaysBlack) {
14648             CallFlagEvent();
14649           }
14650         } else { // white clock
14651           if (gameMode == EditPosition || gameMode == IcsExamining) {
14652             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14653             SetWhiteToPlayEvent();
14654           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14655           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14656           } else if (shiftKey) {
14657             AdjustClock(which, -1);
14658           } else if (gameMode == IcsPlayingBlack ||
14659                    gameMode == MachinePlaysWhite) {
14660             CallFlagEvent();
14661           }
14662         }
14663 }
14664
14665 void
14666 DrawEvent ()
14667 {
14668     /* Offer draw or accept pending draw offer from opponent */
14669
14670     if (appData.icsActive) {
14671         /* Note: tournament rules require draw offers to be
14672            made after you make your move but before you punch
14673            your clock.  Currently ICS doesn't let you do that;
14674            instead, you immediately punch your clock after making
14675            a move, but you can offer a draw at any time. */
14676
14677         SendToICS(ics_prefix);
14678         SendToICS("draw\n");
14679         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14680     } else if (cmailMsgLoaded) {
14681         if (currentMove == cmailOldMove &&
14682             commentList[cmailOldMove] != NULL &&
14683             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14684                    "Black offers a draw" : "White offers a draw")) {
14685             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14686             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14687         } else if (currentMove == cmailOldMove + 1) {
14688             char *offer = WhiteOnMove(cmailOldMove) ?
14689               "White offers a draw" : "Black offers a draw";
14690             AppendComment(currentMove, offer, TRUE);
14691             DisplayComment(currentMove - 1, offer);
14692             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14693         } else {
14694             DisplayError(_("You must make your move before offering a draw"), 0);
14695             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14696         }
14697     } else if (first.offeredDraw) {
14698         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14699     } else {
14700         if (first.sendDrawOffers) {
14701             SendToProgram("draw\n", &first);
14702             userOfferedDraw = TRUE;
14703         }
14704     }
14705 }
14706
14707 void
14708 AdjournEvent ()
14709 {
14710     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14711
14712     if (appData.icsActive) {
14713         SendToICS(ics_prefix);
14714         SendToICS("adjourn\n");
14715     } else {
14716         /* Currently GNU Chess doesn't offer or accept Adjourns */
14717     }
14718 }
14719
14720
14721 void
14722 AbortEvent ()
14723 {
14724     /* Offer Abort or accept pending Abort offer from opponent */
14725
14726     if (appData.icsActive) {
14727         SendToICS(ics_prefix);
14728         SendToICS("abort\n");
14729     } else {
14730         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14731     }
14732 }
14733
14734 void
14735 ResignEvent ()
14736 {
14737     /* Resign.  You can do this even if it's not your turn. */
14738
14739     if (appData.icsActive) {
14740         SendToICS(ics_prefix);
14741         SendToICS("resign\n");
14742     } else {
14743         switch (gameMode) {
14744           case MachinePlaysWhite:
14745             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14746             break;
14747           case MachinePlaysBlack:
14748             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14749             break;
14750           case EditGame:
14751             if (cmailMsgLoaded) {
14752                 TruncateGame();
14753                 if (WhiteOnMove(cmailOldMove)) {
14754                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14755                 } else {
14756                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14757                 }
14758                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14759             }
14760             break;
14761           default:
14762             break;
14763         }
14764     }
14765 }
14766
14767
14768 void
14769 StopObservingEvent ()
14770 {
14771     /* Stop observing current games */
14772     SendToICS(ics_prefix);
14773     SendToICS("unobserve\n");
14774 }
14775
14776 void
14777 StopExaminingEvent ()
14778 {
14779     /* Stop observing current game */
14780     SendToICS(ics_prefix);
14781     SendToICS("unexamine\n");
14782 }
14783
14784 void
14785 ForwardInner (int target)
14786 {
14787     int limit; int oldSeekGraphUp = seekGraphUp;
14788
14789     if (appData.debugMode)
14790         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14791                 target, currentMove, forwardMostMove);
14792
14793     if (gameMode == EditPosition)
14794       return;
14795
14796     seekGraphUp = FALSE;
14797     MarkTargetSquares(1);
14798
14799     if (gameMode == PlayFromGameFile && !pausing)
14800       PauseEvent();
14801
14802     if (gameMode == IcsExamining && pausing)
14803       limit = pauseExamForwardMostMove;
14804     else
14805       limit = forwardMostMove;
14806
14807     if (target > limit) target = limit;
14808
14809     if (target > 0 && moveList[target - 1][0]) {
14810         int fromX, fromY, toX, toY;
14811         toX = moveList[target - 1][2] - AAA;
14812         toY = moveList[target - 1][3] - ONE;
14813         if (moveList[target - 1][1] == '@') {
14814             if (appData.highlightLastMove) {
14815                 SetHighlights(-1, -1, toX, toY);
14816             }
14817         } else {
14818             fromX = moveList[target - 1][0] - AAA;
14819             fromY = moveList[target - 1][1] - ONE;
14820             if (target == currentMove + 1) {
14821                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14822             }
14823             if (appData.highlightLastMove) {
14824                 SetHighlights(fromX, fromY, toX, toY);
14825             }
14826         }
14827     }
14828     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14829         gameMode == Training || gameMode == PlayFromGameFile ||
14830         gameMode == AnalyzeFile) {
14831         while (currentMove < target) {
14832             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14833             SendMoveToProgram(currentMove++, &first);
14834         }
14835     } else {
14836         currentMove = target;
14837     }
14838
14839     if (gameMode == EditGame || gameMode == EndOfGame) {
14840         whiteTimeRemaining = timeRemaining[0][currentMove];
14841         blackTimeRemaining = timeRemaining[1][currentMove];
14842     }
14843     DisplayBothClocks();
14844     DisplayMove(currentMove - 1);
14845     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14846     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14847     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14848         DisplayComment(currentMove - 1, commentList[currentMove]);
14849     }
14850     ClearMap(); // [HGM] exclude: invalidate map
14851 }
14852
14853
14854 void
14855 ForwardEvent ()
14856 {
14857     if (gameMode == IcsExamining && !pausing) {
14858         SendToICS(ics_prefix);
14859         SendToICS("forward\n");
14860     } else {
14861         ForwardInner(currentMove + 1);
14862     }
14863 }
14864
14865 void
14866 ToEndEvent ()
14867 {
14868     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14869         /* to optimze, we temporarily turn off analysis mode while we feed
14870          * the remaining moves to the engine. Otherwise we get analysis output
14871          * after each move.
14872          */
14873         if (first.analysisSupport) {
14874           SendToProgram("exit\nforce\n", &first);
14875           first.analyzing = FALSE;
14876         }
14877     }
14878
14879     if (gameMode == IcsExamining && !pausing) {
14880         SendToICS(ics_prefix);
14881         SendToICS("forward 999999\n");
14882     } else {
14883         ForwardInner(forwardMostMove);
14884     }
14885
14886     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14887         /* we have fed all the moves, so reactivate analysis mode */
14888         SendToProgram("analyze\n", &first);
14889         first.analyzing = TRUE;
14890         /*first.maybeThinking = TRUE;*/
14891         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14892     }
14893 }
14894
14895 void
14896 BackwardInner (int target)
14897 {
14898     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14899
14900     if (appData.debugMode)
14901         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14902                 target, currentMove, forwardMostMove);
14903
14904     if (gameMode == EditPosition) return;
14905     seekGraphUp = FALSE;
14906     MarkTargetSquares(1);
14907     if (currentMove <= backwardMostMove) {
14908         ClearHighlights();
14909         DrawPosition(full_redraw, boards[currentMove]);
14910         return;
14911     }
14912     if (gameMode == PlayFromGameFile && !pausing)
14913       PauseEvent();
14914
14915     if (moveList[target][0]) {
14916         int fromX, fromY, toX, toY;
14917         toX = moveList[target][2] - AAA;
14918         toY = moveList[target][3] - ONE;
14919         if (moveList[target][1] == '@') {
14920             if (appData.highlightLastMove) {
14921                 SetHighlights(-1, -1, toX, toY);
14922             }
14923         } else {
14924             fromX = moveList[target][0] - AAA;
14925             fromY = moveList[target][1] - ONE;
14926             if (target == currentMove - 1) {
14927                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14928             }
14929             if (appData.highlightLastMove) {
14930                 SetHighlights(fromX, fromY, toX, toY);
14931             }
14932         }
14933     }
14934     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14935         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14936         while (currentMove > target) {
14937             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14938                 // null move cannot be undone. Reload program with move history before it.
14939                 int i;
14940                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14941                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14942                 }
14943                 SendBoard(&first, i);
14944               if(second.analyzing) SendBoard(&second, i);
14945                 for(currentMove=i; currentMove<target; currentMove++) {
14946                     SendMoveToProgram(currentMove, &first);
14947                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14948                 }
14949                 break;
14950             }
14951             SendToBoth("undo\n");
14952             currentMove--;
14953         }
14954     } else {
14955         currentMove = target;
14956     }
14957
14958     if (gameMode == EditGame || gameMode == EndOfGame) {
14959         whiteTimeRemaining = timeRemaining[0][currentMove];
14960         blackTimeRemaining = timeRemaining[1][currentMove];
14961     }
14962     DisplayBothClocks();
14963     DisplayMove(currentMove - 1);
14964     DrawPosition(full_redraw, boards[currentMove]);
14965     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14966     // [HGM] PV info: routine tests if comment empty
14967     DisplayComment(currentMove - 1, commentList[currentMove]);
14968     ClearMap(); // [HGM] exclude: invalidate map
14969 }
14970
14971 void
14972 BackwardEvent ()
14973 {
14974     if (gameMode == IcsExamining && !pausing) {
14975         SendToICS(ics_prefix);
14976         SendToICS("backward\n");
14977     } else {
14978         BackwardInner(currentMove - 1);
14979     }
14980 }
14981
14982 void
14983 ToStartEvent ()
14984 {
14985     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14986         /* to optimize, we temporarily turn off analysis mode while we undo
14987          * all the moves. Otherwise we get analysis output after each undo.
14988          */
14989         if (first.analysisSupport) {
14990           SendToProgram("exit\nforce\n", &first);
14991           first.analyzing = FALSE;
14992         }
14993     }
14994
14995     if (gameMode == IcsExamining && !pausing) {
14996         SendToICS(ics_prefix);
14997         SendToICS("backward 999999\n");
14998     } else {
14999         BackwardInner(backwardMostMove);
15000     }
15001
15002     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15003         /* we have fed all the moves, so reactivate analysis mode */
15004         SendToProgram("analyze\n", &first);
15005         first.analyzing = TRUE;
15006         /*first.maybeThinking = TRUE;*/
15007         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15008     }
15009 }
15010
15011 void
15012 ToNrEvent (int to)
15013 {
15014   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15015   if (to >= forwardMostMove) to = forwardMostMove;
15016   if (to <= backwardMostMove) to = backwardMostMove;
15017   if (to < currentMove) {
15018     BackwardInner(to);
15019   } else {
15020     ForwardInner(to);
15021   }
15022 }
15023
15024 void
15025 RevertEvent (Boolean annotate)
15026 {
15027     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15028         return;
15029     }
15030     if (gameMode != IcsExamining) {
15031         DisplayError(_("You are not examining a game"), 0);
15032         return;
15033     }
15034     if (pausing) {
15035         DisplayError(_("You can't revert while pausing"), 0);
15036         return;
15037     }
15038     SendToICS(ics_prefix);
15039     SendToICS("revert\n");
15040 }
15041
15042 void
15043 RetractMoveEvent ()
15044 {
15045     switch (gameMode) {
15046       case MachinePlaysWhite:
15047       case MachinePlaysBlack:
15048         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15049             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15050             return;
15051         }
15052         if (forwardMostMove < 2) return;
15053         currentMove = forwardMostMove = forwardMostMove - 2;
15054         whiteTimeRemaining = timeRemaining[0][currentMove];
15055         blackTimeRemaining = timeRemaining[1][currentMove];
15056         DisplayBothClocks();
15057         DisplayMove(currentMove - 1);
15058         ClearHighlights();/*!! could figure this out*/
15059         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15060         SendToProgram("remove\n", &first);
15061         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15062         break;
15063
15064       case BeginningOfGame:
15065       default:
15066         break;
15067
15068       case IcsPlayingWhite:
15069       case IcsPlayingBlack:
15070         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15071             SendToICS(ics_prefix);
15072             SendToICS("takeback 2\n");
15073         } else {
15074             SendToICS(ics_prefix);
15075             SendToICS("takeback 1\n");
15076         }
15077         break;
15078     }
15079 }
15080
15081 void
15082 MoveNowEvent ()
15083 {
15084     ChessProgramState *cps;
15085
15086     switch (gameMode) {
15087       case MachinePlaysWhite:
15088         if (!WhiteOnMove(forwardMostMove)) {
15089             DisplayError(_("It is your turn"), 0);
15090             return;
15091         }
15092         cps = &first;
15093         break;
15094       case MachinePlaysBlack:
15095         if (WhiteOnMove(forwardMostMove)) {
15096             DisplayError(_("It is your turn"), 0);
15097             return;
15098         }
15099         cps = &first;
15100         break;
15101       case TwoMachinesPlay:
15102         if (WhiteOnMove(forwardMostMove) ==
15103             (first.twoMachinesColor[0] == 'w')) {
15104             cps = &first;
15105         } else {
15106             cps = &second;
15107         }
15108         break;
15109       case BeginningOfGame:
15110       default:
15111         return;
15112     }
15113     SendToProgram("?\n", cps);
15114 }
15115
15116 void
15117 TruncateGameEvent ()
15118 {
15119     EditGameEvent();
15120     if (gameMode != EditGame) return;
15121     TruncateGame();
15122 }
15123
15124 void
15125 TruncateGame ()
15126 {
15127     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15128     if (forwardMostMove > currentMove) {
15129         if (gameInfo.resultDetails != NULL) {
15130             free(gameInfo.resultDetails);
15131             gameInfo.resultDetails = NULL;
15132             gameInfo.result = GameUnfinished;
15133         }
15134         forwardMostMove = currentMove;
15135         HistorySet(parseList, backwardMostMove, forwardMostMove,
15136                    currentMove-1);
15137     }
15138 }
15139
15140 void
15141 HintEvent ()
15142 {
15143     if (appData.noChessProgram) return;
15144     switch (gameMode) {
15145       case MachinePlaysWhite:
15146         if (WhiteOnMove(forwardMostMove)) {
15147             DisplayError(_("Wait until your turn"), 0);
15148             return;
15149         }
15150         break;
15151       case BeginningOfGame:
15152       case MachinePlaysBlack:
15153         if (!WhiteOnMove(forwardMostMove)) {
15154             DisplayError(_("Wait until your turn"), 0);
15155             return;
15156         }
15157         break;
15158       default:
15159         DisplayError(_("No hint available"), 0);
15160         return;
15161     }
15162     SendToProgram("hint\n", &first);
15163     hintRequested = TRUE;
15164 }
15165
15166 void
15167 CreateBookEvent ()
15168 {
15169     ListGame * lg = (ListGame *) gameList.head;
15170     FILE *f;
15171     int nItem;
15172     static int secondTime = FALSE;
15173
15174     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15175         DisplayError(_("Game list not loaded or empty"), 0);
15176         return;
15177     }
15178
15179     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15180         fclose(f);
15181         secondTime++;
15182         DisplayNote(_("Book file exists! Try again for overwrite."));
15183         return;
15184     }
15185
15186     creatingBook = TRUE;
15187     secondTime = FALSE;
15188
15189     /* Get list size */
15190     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15191         LoadGame(f, nItem, "", TRUE);
15192         AddGameToBook(TRUE);
15193         lg = (ListGame *) lg->node.succ;
15194     }
15195
15196     creatingBook = FALSE;
15197     FlushBook();
15198 }
15199
15200 void
15201 BookEvent ()
15202 {
15203     if (appData.noChessProgram) return;
15204     switch (gameMode) {
15205       case MachinePlaysWhite:
15206         if (WhiteOnMove(forwardMostMove)) {
15207             DisplayError(_("Wait until your turn"), 0);
15208             return;
15209         }
15210         break;
15211       case BeginningOfGame:
15212       case MachinePlaysBlack:
15213         if (!WhiteOnMove(forwardMostMove)) {
15214             DisplayError(_("Wait until your turn"), 0);
15215             return;
15216         }
15217         break;
15218       case EditPosition:
15219         EditPositionDone(TRUE);
15220         break;
15221       case TwoMachinesPlay:
15222         return;
15223       default:
15224         break;
15225     }
15226     SendToProgram("bk\n", &first);
15227     bookOutput[0] = NULLCHAR;
15228     bookRequested = TRUE;
15229 }
15230
15231 void
15232 AboutGameEvent ()
15233 {
15234     char *tags = PGNTags(&gameInfo);
15235     TagsPopUp(tags, CmailMsg());
15236     free(tags);
15237 }
15238
15239 /* end button procedures */
15240
15241 void
15242 PrintPosition (FILE *fp, int move)
15243 {
15244     int i, j;
15245
15246     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15247         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15248             char c = PieceToChar(boards[move][i][j]);
15249             fputc(c == 'x' ? '.' : c, fp);
15250             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15251         }
15252     }
15253     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15254       fprintf(fp, "white to play\n");
15255     else
15256       fprintf(fp, "black to play\n");
15257 }
15258
15259 void
15260 PrintOpponents (FILE *fp)
15261 {
15262     if (gameInfo.white != NULL) {
15263         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15264     } else {
15265         fprintf(fp, "\n");
15266     }
15267 }
15268
15269 /* Find last component of program's own name, using some heuristics */
15270 void
15271 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15272 {
15273     char *p, *q, c;
15274     int local = (strcmp(host, "localhost") == 0);
15275     while (!local && (p = strchr(prog, ';')) != NULL) {
15276         p++;
15277         while (*p == ' ') p++;
15278         prog = p;
15279     }
15280     if (*prog == '"' || *prog == '\'') {
15281         q = strchr(prog + 1, *prog);
15282     } else {
15283         q = strchr(prog, ' ');
15284     }
15285     if (q == NULL) q = prog + strlen(prog);
15286     p = q;
15287     while (p >= prog && *p != '/' && *p != '\\') p--;
15288     p++;
15289     if(p == prog && *p == '"') p++;
15290     c = *q; *q = 0;
15291     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15292     memcpy(buf, p, q - p);
15293     buf[q - p] = NULLCHAR;
15294     if (!local) {
15295         strcat(buf, "@");
15296         strcat(buf, host);
15297     }
15298 }
15299
15300 char *
15301 TimeControlTagValue ()
15302 {
15303     char buf[MSG_SIZ];
15304     if (!appData.clockMode) {
15305       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15306     } else if (movesPerSession > 0) {
15307       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15308     } else if (timeIncrement == 0) {
15309       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15310     } else {
15311       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15312     }
15313     return StrSave(buf);
15314 }
15315
15316 void
15317 SetGameInfo ()
15318 {
15319     /* This routine is used only for certain modes */
15320     VariantClass v = gameInfo.variant;
15321     ChessMove r = GameUnfinished;
15322     char *p = NULL;
15323
15324     if(keepInfo) return;
15325
15326     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15327         r = gameInfo.result;
15328         p = gameInfo.resultDetails;
15329         gameInfo.resultDetails = NULL;
15330     }
15331     ClearGameInfo(&gameInfo);
15332     gameInfo.variant = v;
15333
15334     switch (gameMode) {
15335       case MachinePlaysWhite:
15336         gameInfo.event = StrSave( appData.pgnEventHeader );
15337         gameInfo.site = StrSave(HostName());
15338         gameInfo.date = PGNDate();
15339         gameInfo.round = StrSave("-");
15340         gameInfo.white = StrSave(first.tidy);
15341         gameInfo.black = StrSave(UserName());
15342         gameInfo.timeControl = TimeControlTagValue();
15343         break;
15344
15345       case MachinePlaysBlack:
15346         gameInfo.event = StrSave( appData.pgnEventHeader );
15347         gameInfo.site = StrSave(HostName());
15348         gameInfo.date = PGNDate();
15349         gameInfo.round = StrSave("-");
15350         gameInfo.white = StrSave(UserName());
15351         gameInfo.black = StrSave(first.tidy);
15352         gameInfo.timeControl = TimeControlTagValue();
15353         break;
15354
15355       case TwoMachinesPlay:
15356         gameInfo.event = StrSave( appData.pgnEventHeader );
15357         gameInfo.site = StrSave(HostName());
15358         gameInfo.date = PGNDate();
15359         if (roundNr > 0) {
15360             char buf[MSG_SIZ];
15361             snprintf(buf, MSG_SIZ, "%d", roundNr);
15362             gameInfo.round = StrSave(buf);
15363         } else {
15364             gameInfo.round = StrSave("-");
15365         }
15366         if (first.twoMachinesColor[0] == 'w') {
15367             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15368             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15369         } else {
15370             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15371             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15372         }
15373         gameInfo.timeControl = TimeControlTagValue();
15374         break;
15375
15376       case EditGame:
15377         gameInfo.event = StrSave("Edited game");
15378         gameInfo.site = StrSave(HostName());
15379         gameInfo.date = PGNDate();
15380         gameInfo.round = StrSave("-");
15381         gameInfo.white = StrSave("-");
15382         gameInfo.black = StrSave("-");
15383         gameInfo.result = r;
15384         gameInfo.resultDetails = p;
15385         break;
15386
15387       case EditPosition:
15388         gameInfo.event = StrSave("Edited position");
15389         gameInfo.site = StrSave(HostName());
15390         gameInfo.date = PGNDate();
15391         gameInfo.round = StrSave("-");
15392         gameInfo.white = StrSave("-");
15393         gameInfo.black = StrSave("-");
15394         break;
15395
15396       case IcsPlayingWhite:
15397       case IcsPlayingBlack:
15398       case IcsObserving:
15399       case IcsExamining:
15400         break;
15401
15402       case PlayFromGameFile:
15403         gameInfo.event = StrSave("Game from non-PGN file");
15404         gameInfo.site = StrSave(HostName());
15405         gameInfo.date = PGNDate();
15406         gameInfo.round = StrSave("-");
15407         gameInfo.white = StrSave("?");
15408         gameInfo.black = StrSave("?");
15409         break;
15410
15411       default:
15412         break;
15413     }
15414 }
15415
15416 void
15417 ReplaceComment (int index, char *text)
15418 {
15419     int len;
15420     char *p;
15421     float score;
15422
15423     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15424        pvInfoList[index-1].depth == len &&
15425        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15426        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15427     while (*text == '\n') text++;
15428     len = strlen(text);
15429     while (len > 0 && text[len - 1] == '\n') len--;
15430
15431     if (commentList[index] != NULL)
15432       free(commentList[index]);
15433
15434     if (len == 0) {
15435         commentList[index] = NULL;
15436         return;
15437     }
15438   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15439       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15440       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15441     commentList[index] = (char *) malloc(len + 2);
15442     strncpy(commentList[index], text, len);
15443     commentList[index][len] = '\n';
15444     commentList[index][len + 1] = NULLCHAR;
15445   } else {
15446     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15447     char *p;
15448     commentList[index] = (char *) malloc(len + 7);
15449     safeStrCpy(commentList[index], "{\n", 3);
15450     safeStrCpy(commentList[index]+2, text, len+1);
15451     commentList[index][len+2] = NULLCHAR;
15452     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15453     strcat(commentList[index], "\n}\n");
15454   }
15455 }
15456
15457 void
15458 CrushCRs (char *text)
15459 {
15460   char *p = text;
15461   char *q = text;
15462   char ch;
15463
15464   do {
15465     ch = *p++;
15466     if (ch == '\r') continue;
15467     *q++ = ch;
15468   } while (ch != '\0');
15469 }
15470
15471 void
15472 AppendComment (int index, char *text, Boolean addBraces)
15473 /* addBraces  tells if we should add {} */
15474 {
15475     int oldlen, len;
15476     char *old;
15477
15478 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15479     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15480
15481     CrushCRs(text);
15482     while (*text == '\n') text++;
15483     len = strlen(text);
15484     while (len > 0 && text[len - 1] == '\n') len--;
15485     text[len] = NULLCHAR;
15486
15487     if (len == 0) return;
15488
15489     if (commentList[index] != NULL) {
15490       Boolean addClosingBrace = addBraces;
15491         old = commentList[index];
15492         oldlen = strlen(old);
15493         while(commentList[index][oldlen-1] ==  '\n')
15494           commentList[index][--oldlen] = NULLCHAR;
15495         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15496         safeStrCpy(commentList[index], old, oldlen + len + 6);
15497         free(old);
15498         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15499         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15500           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15501           while (*text == '\n') { text++; len--; }
15502           commentList[index][--oldlen] = NULLCHAR;
15503       }
15504         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15505         else          strcat(commentList[index], "\n");
15506         strcat(commentList[index], text);
15507         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15508         else          strcat(commentList[index], "\n");
15509     } else {
15510         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15511         if(addBraces)
15512           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15513         else commentList[index][0] = NULLCHAR;
15514         strcat(commentList[index], text);
15515         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15516         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15517     }
15518 }
15519
15520 static char *
15521 FindStr (char * text, char * sub_text)
15522 {
15523     char * result = strstr( text, sub_text );
15524
15525     if( result != NULL ) {
15526         result += strlen( sub_text );
15527     }
15528
15529     return result;
15530 }
15531
15532 /* [AS] Try to extract PV info from PGN comment */
15533 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15534 char *
15535 GetInfoFromComment (int index, char * text)
15536 {
15537     char * sep = text, *p;
15538
15539     if( text != NULL && index > 0 ) {
15540         int score = 0;
15541         int depth = 0;
15542         int time = -1, sec = 0, deci;
15543         char * s_eval = FindStr( text, "[%eval " );
15544         char * s_emt = FindStr( text, "[%emt " );
15545
15546         if( s_eval != NULL || s_emt != NULL ) {
15547             /* New style */
15548             char delim;
15549
15550             if( s_eval != NULL ) {
15551                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15552                     return text;
15553                 }
15554
15555                 if( delim != ']' ) {
15556                     return text;
15557                 }
15558             }
15559
15560             if( s_emt != NULL ) {
15561             }
15562                 return text;
15563         }
15564         else {
15565             /* We expect something like: [+|-]nnn.nn/dd */
15566             int score_lo = 0;
15567
15568             if(*text != '{') return text; // [HGM] braces: must be normal comment
15569
15570             sep = strchr( text, '/' );
15571             if( sep == NULL || sep < (text+4) ) {
15572                 return text;
15573             }
15574
15575             p = text;
15576             if(p[1] == '(') { // comment starts with PV
15577                p = strchr(p, ')'); // locate end of PV
15578                if(p == NULL || sep < p+5) return text;
15579                // at this point we have something like "{(.*) +0.23/6 ..."
15580                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15581                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15582                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15583             }
15584             time = -1; sec = -1; deci = -1;
15585             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15586                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15587                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15588                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15589                 return text;
15590             }
15591
15592             if( score_lo < 0 || score_lo >= 100 ) {
15593                 return text;
15594             }
15595
15596             if(sec >= 0) time = 600*time + 10*sec; else
15597             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15598
15599             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15600
15601             /* [HGM] PV time: now locate end of PV info */
15602             while( *++sep >= '0' && *sep <= '9'); // strip depth
15603             if(time >= 0)
15604             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15605             if(sec >= 0)
15606             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15607             if(deci >= 0)
15608             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15609             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15610         }
15611
15612         if( depth <= 0 ) {
15613             return text;
15614         }
15615
15616         if( time < 0 ) {
15617             time = -1;
15618         }
15619
15620         pvInfoList[index-1].depth = depth;
15621         pvInfoList[index-1].score = score;
15622         pvInfoList[index-1].time  = 10*time; // centi-sec
15623         if(*sep == '}') *sep = 0; else *--sep = '{';
15624         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15625     }
15626     return sep;
15627 }
15628
15629 void
15630 SendToProgram (char *message, ChessProgramState *cps)
15631 {
15632     int count, outCount, error;
15633     char buf[MSG_SIZ];
15634
15635     if (cps->pr == NoProc) return;
15636     Attention(cps);
15637
15638     if (appData.debugMode) {
15639         TimeMark now;
15640         GetTimeMark(&now);
15641         fprintf(debugFP, "%ld >%-6s: %s",
15642                 SubtractTimeMarks(&now, &programStartTime),
15643                 cps->which, message);
15644         if(serverFP)
15645             fprintf(serverFP, "%ld >%-6s: %s",
15646                 SubtractTimeMarks(&now, &programStartTime),
15647                 cps->which, message), fflush(serverFP);
15648     }
15649
15650     count = strlen(message);
15651     outCount = OutputToProcess(cps->pr, message, count, &error);
15652     if (outCount < count && !exiting
15653                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15654       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15655       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15656         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15657             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15658                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15659                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15660                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15661             } else {
15662                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15663                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15664                 gameInfo.result = res;
15665             }
15666             gameInfo.resultDetails = StrSave(buf);
15667         }
15668         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15669         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15670     }
15671 }
15672
15673 void
15674 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15675 {
15676     char *end_str;
15677     char buf[MSG_SIZ];
15678     ChessProgramState *cps = (ChessProgramState *)closure;
15679
15680     if (isr != cps->isr) return; /* Killed intentionally */
15681     if (count <= 0) {
15682         if (count == 0) {
15683             RemoveInputSource(cps->isr);
15684             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15685                     _(cps->which), cps->program);
15686             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15687             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15688                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15689                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15690                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15691                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15692                 } else {
15693                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15694                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15695                     gameInfo.result = res;
15696                 }
15697                 gameInfo.resultDetails = StrSave(buf);
15698             }
15699             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15700             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15701         } else {
15702             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15703                     _(cps->which), cps->program);
15704             RemoveInputSource(cps->isr);
15705
15706             /* [AS] Program is misbehaving badly... kill it */
15707             if( count == -2 ) {
15708                 DestroyChildProcess( cps->pr, 9 );
15709                 cps->pr = NoProc;
15710             }
15711
15712             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15713         }
15714         return;
15715     }
15716
15717     if ((end_str = strchr(message, '\r')) != NULL)
15718       *end_str = NULLCHAR;
15719     if ((end_str = strchr(message, '\n')) != NULL)
15720       *end_str = NULLCHAR;
15721
15722     if (appData.debugMode) {
15723         TimeMark now; int print = 1;
15724         char *quote = ""; char c; int i;
15725
15726         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15727                 char start = message[0];
15728                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15729                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15730                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15731                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15732                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15733                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15734                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15735                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15736                    sscanf(message, "hint: %c", &c)!=1 &&
15737                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15738                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15739                     print = (appData.engineComments >= 2);
15740                 }
15741                 message[0] = start; // restore original message
15742         }
15743         if(print) {
15744                 GetTimeMark(&now);
15745                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15746                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15747                         quote,
15748                         message);
15749                 if(serverFP)
15750                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15751                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15752                         quote,
15753                         message), fflush(serverFP);
15754         }
15755     }
15756
15757     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15758     if (appData.icsEngineAnalyze) {
15759         if (strstr(message, "whisper") != NULL ||
15760              strstr(message, "kibitz") != NULL ||
15761             strstr(message, "tellics") != NULL) return;
15762     }
15763
15764     HandleMachineMove(message, cps);
15765 }
15766
15767
15768 void
15769 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15770 {
15771     char buf[MSG_SIZ];
15772     int seconds;
15773
15774     if( timeControl_2 > 0 ) {
15775         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15776             tc = timeControl_2;
15777         }
15778     }
15779     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15780     inc /= cps->timeOdds;
15781     st  /= cps->timeOdds;
15782
15783     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15784
15785     if (st > 0) {
15786       /* Set exact time per move, normally using st command */
15787       if (cps->stKludge) {
15788         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15789         seconds = st % 60;
15790         if (seconds == 0) {
15791           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15792         } else {
15793           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15794         }
15795       } else {
15796         snprintf(buf, MSG_SIZ, "st %d\n", st);
15797       }
15798     } else {
15799       /* Set conventional or incremental time control, using level command */
15800       if (seconds == 0) {
15801         /* Note old gnuchess bug -- minutes:seconds used to not work.
15802            Fixed in later versions, but still avoid :seconds
15803            when seconds is 0. */
15804         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15805       } else {
15806         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15807                  seconds, inc/1000.);
15808       }
15809     }
15810     SendToProgram(buf, cps);
15811
15812     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15813     /* Orthogonally, limit search to given depth */
15814     if (sd > 0) {
15815       if (cps->sdKludge) {
15816         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15817       } else {
15818         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15819       }
15820       SendToProgram(buf, cps);
15821     }
15822
15823     if(cps->nps >= 0) { /* [HGM] nps */
15824         if(cps->supportsNPS == FALSE)
15825           cps->nps = -1; // don't use if engine explicitly says not supported!
15826         else {
15827           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15828           SendToProgram(buf, cps);
15829         }
15830     }
15831 }
15832
15833 ChessProgramState *
15834 WhitePlayer ()
15835 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15836 {
15837     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15838        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15839         return &second;
15840     return &first;
15841 }
15842
15843 void
15844 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15845 {
15846     char message[MSG_SIZ];
15847     long time, otime;
15848
15849     /* Note: this routine must be called when the clocks are stopped
15850        or when they have *just* been set or switched; otherwise
15851        it will be off by the time since the current tick started.
15852     */
15853     if (machineWhite) {
15854         time = whiteTimeRemaining / 10;
15855         otime = blackTimeRemaining / 10;
15856     } else {
15857         time = blackTimeRemaining / 10;
15858         otime = whiteTimeRemaining / 10;
15859     }
15860     /* [HGM] translate opponent's time by time-odds factor */
15861     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15862
15863     if (time <= 0) time = 1;
15864     if (otime <= 0) otime = 1;
15865
15866     snprintf(message, MSG_SIZ, "time %ld\n", time);
15867     SendToProgram(message, cps);
15868
15869     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15870     SendToProgram(message, cps);
15871 }
15872
15873 int
15874 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15875 {
15876   char buf[MSG_SIZ];
15877   int len = strlen(name);
15878   int val;
15879
15880   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15881     (*p) += len + 1;
15882     sscanf(*p, "%d", &val);
15883     *loc = (val != 0);
15884     while (**p && **p != ' ')
15885       (*p)++;
15886     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15887     SendToProgram(buf, cps);
15888     return TRUE;
15889   }
15890   return FALSE;
15891 }
15892
15893 int
15894 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15895 {
15896   char buf[MSG_SIZ];
15897   int len = strlen(name);
15898   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15899     (*p) += len + 1;
15900     sscanf(*p, "%d", loc);
15901     while (**p && **p != ' ') (*p)++;
15902     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15903     SendToProgram(buf, cps);
15904     return TRUE;
15905   }
15906   return FALSE;
15907 }
15908
15909 int
15910 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15911 {
15912   char buf[MSG_SIZ];
15913   int len = strlen(name);
15914   if (strncmp((*p), name, len) == 0
15915       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15916     (*p) += len + 2;
15917     sscanf(*p, "%[^\"]", loc);
15918     while (**p && **p != '\"') (*p)++;
15919     if (**p == '\"') (*p)++;
15920     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15921     SendToProgram(buf, cps);
15922     return TRUE;
15923   }
15924   return FALSE;
15925 }
15926
15927 int
15928 ParseOption (Option *opt, ChessProgramState *cps)
15929 // [HGM] options: process the string that defines an engine option, and determine
15930 // name, type, default value, and allowed value range
15931 {
15932         char *p, *q, buf[MSG_SIZ];
15933         int n, min = (-1)<<31, max = 1<<31, def;
15934
15935         if(p = strstr(opt->name, " -spin ")) {
15936             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15937             if(max < min) max = min; // enforce consistency
15938             if(def < min) def = min;
15939             if(def > max) def = max;
15940             opt->value = def;
15941             opt->min = min;
15942             opt->max = max;
15943             opt->type = Spin;
15944         } else if((p = strstr(opt->name, " -slider "))) {
15945             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15946             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15947             if(max < min) max = min; // enforce consistency
15948             if(def < min) def = min;
15949             if(def > max) def = max;
15950             opt->value = def;
15951             opt->min = min;
15952             opt->max = max;
15953             opt->type = Spin; // Slider;
15954         } else if((p = strstr(opt->name, " -string "))) {
15955             opt->textValue = p+9;
15956             opt->type = TextBox;
15957         } else if((p = strstr(opt->name, " -file "))) {
15958             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15959             opt->textValue = p+7;
15960             opt->type = FileName; // FileName;
15961         } else if((p = strstr(opt->name, " -path "))) {
15962             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15963             opt->textValue = p+7;
15964             opt->type = PathName; // PathName;
15965         } else if(p = strstr(opt->name, " -check ")) {
15966             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15967             opt->value = (def != 0);
15968             opt->type = CheckBox;
15969         } else if(p = strstr(opt->name, " -combo ")) {
15970             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15971             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15972             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15973             opt->value = n = 0;
15974             while(q = StrStr(q, " /// ")) {
15975                 n++; *q = 0;    // count choices, and null-terminate each of them
15976                 q += 5;
15977                 if(*q == '*') { // remember default, which is marked with * prefix
15978                     q++;
15979                     opt->value = n;
15980                 }
15981                 cps->comboList[cps->comboCnt++] = q;
15982             }
15983             cps->comboList[cps->comboCnt++] = NULL;
15984             opt->max = n + 1;
15985             opt->type = ComboBox;
15986         } else if(p = strstr(opt->name, " -button")) {
15987             opt->type = Button;
15988         } else if(p = strstr(opt->name, " -save")) {
15989             opt->type = SaveButton;
15990         } else return FALSE;
15991         *p = 0; // terminate option name
15992         // now look if the command-line options define a setting for this engine option.
15993         if(cps->optionSettings && cps->optionSettings[0])
15994             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15995         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15996           snprintf(buf, MSG_SIZ, "option %s", p);
15997                 if(p = strstr(buf, ",")) *p = 0;
15998                 if(q = strchr(buf, '=')) switch(opt->type) {
15999                     case ComboBox:
16000                         for(n=0; n<opt->max; n++)
16001                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16002                         break;
16003                     case TextBox:
16004                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16005                         break;
16006                     case Spin:
16007                     case CheckBox:
16008                         opt->value = atoi(q+1);
16009                     default:
16010                         break;
16011                 }
16012                 strcat(buf, "\n");
16013                 SendToProgram(buf, cps);
16014         }
16015         return TRUE;
16016 }
16017
16018 void
16019 FeatureDone (ChessProgramState *cps, int val)
16020 {
16021   DelayedEventCallback cb = GetDelayedEvent();
16022   if ((cb == InitBackEnd3 && cps == &first) ||
16023       (cb == SettingsMenuIfReady && cps == &second) ||
16024       (cb == LoadEngine) ||
16025       (cb == TwoMachinesEventIfReady)) {
16026     CancelDelayedEvent();
16027     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16028   }
16029   cps->initDone = val;
16030   if(val) cps->reload = FALSE;
16031 }
16032
16033 /* Parse feature command from engine */
16034 void
16035 ParseFeatures (char *args, ChessProgramState *cps)
16036 {
16037   char *p = args;
16038   char *q;
16039   int val;
16040   char buf[MSG_SIZ];
16041
16042   for (;;) {
16043     while (*p == ' ') p++;
16044     if (*p == NULLCHAR) return;
16045
16046     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16047     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16048     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16049     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16050     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16051     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16052     if (BoolFeature(&p, "reuse", &val, cps)) {
16053       /* Engine can disable reuse, but can't enable it if user said no */
16054       if (!val) cps->reuse = FALSE;
16055       continue;
16056     }
16057     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16058     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16059       if (gameMode == TwoMachinesPlay) {
16060         DisplayTwoMachinesTitle();
16061       } else {
16062         DisplayTitle("");
16063       }
16064       continue;
16065     }
16066     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16067     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16068     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16069     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16070     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16071     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16072     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16073     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16074     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16075     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16076     if (IntFeature(&p, "done", &val, cps)) {
16077       FeatureDone(cps, val);
16078       continue;
16079     }
16080     /* Added by Tord: */
16081     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16082     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16083     /* End of additions by Tord */
16084
16085     /* [HGM] added features: */
16086     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16087     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16088     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16089     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16090     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16091     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16092     if (StringFeature(&p, "option", buf, cps)) {
16093         if(cps->reload) continue; // we are reloading because of xreuse
16094         FREE(cps->option[cps->nrOptions].name);
16095         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16096         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16097         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16098           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16099             SendToProgram(buf, cps);
16100             continue;
16101         }
16102         if(cps->nrOptions >= MAX_OPTIONS) {
16103             cps->nrOptions--;
16104             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16105             DisplayError(buf, 0);
16106         }
16107         continue;
16108     }
16109     /* End of additions by HGM */
16110
16111     /* unknown feature: complain and skip */
16112     q = p;
16113     while (*q && *q != '=') q++;
16114     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16115     SendToProgram(buf, cps);
16116     p = q;
16117     if (*p == '=') {
16118       p++;
16119       if (*p == '\"') {
16120         p++;
16121         while (*p && *p != '\"') p++;
16122         if (*p == '\"') p++;
16123       } else {
16124         while (*p && *p != ' ') p++;
16125       }
16126     }
16127   }
16128
16129 }
16130
16131 void
16132 PeriodicUpdatesEvent (int newState)
16133 {
16134     if (newState == appData.periodicUpdates)
16135       return;
16136
16137     appData.periodicUpdates=newState;
16138
16139     /* Display type changes, so update it now */
16140 //    DisplayAnalysis();
16141
16142     /* Get the ball rolling again... */
16143     if (newState) {
16144         AnalysisPeriodicEvent(1);
16145         StartAnalysisClock();
16146     }
16147 }
16148
16149 void
16150 PonderNextMoveEvent (int newState)
16151 {
16152     if (newState == appData.ponderNextMove) return;
16153     if (gameMode == EditPosition) EditPositionDone(TRUE);
16154     if (newState) {
16155         SendToProgram("hard\n", &first);
16156         if (gameMode == TwoMachinesPlay) {
16157             SendToProgram("hard\n", &second);
16158         }
16159     } else {
16160         SendToProgram("easy\n", &first);
16161         thinkOutput[0] = NULLCHAR;
16162         if (gameMode == TwoMachinesPlay) {
16163             SendToProgram("easy\n", &second);
16164         }
16165     }
16166     appData.ponderNextMove = newState;
16167 }
16168
16169 void
16170 NewSettingEvent (int option, int *feature, char *command, int value)
16171 {
16172     char buf[MSG_SIZ];
16173
16174     if (gameMode == EditPosition) EditPositionDone(TRUE);
16175     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16176     if(feature == NULL || *feature) SendToProgram(buf, &first);
16177     if (gameMode == TwoMachinesPlay) {
16178         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16179     }
16180 }
16181
16182 void
16183 ShowThinkingEvent ()
16184 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16185 {
16186     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16187     int newState = appData.showThinking
16188         // [HGM] thinking: other features now need thinking output as well
16189         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16190
16191     if (oldState == newState) return;
16192     oldState = newState;
16193     if (gameMode == EditPosition) EditPositionDone(TRUE);
16194     if (oldState) {
16195         SendToProgram("post\n", &first);
16196         if (gameMode == TwoMachinesPlay) {
16197             SendToProgram("post\n", &second);
16198         }
16199     } else {
16200         SendToProgram("nopost\n", &first);
16201         thinkOutput[0] = NULLCHAR;
16202         if (gameMode == TwoMachinesPlay) {
16203             SendToProgram("nopost\n", &second);
16204         }
16205     }
16206 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16207 }
16208
16209 void
16210 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16211 {
16212   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16213   if (pr == NoProc) return;
16214   AskQuestion(title, question, replyPrefix, pr);
16215 }
16216
16217 void
16218 TypeInEvent (char firstChar)
16219 {
16220     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16221         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16222         gameMode == AnalyzeMode || gameMode == EditGame ||
16223         gameMode == EditPosition || gameMode == IcsExamining ||
16224         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16225         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16226                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16227                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16228         gameMode == Training) PopUpMoveDialog(firstChar);
16229 }
16230
16231 void
16232 TypeInDoneEvent (char *move)
16233 {
16234         Board board;
16235         int n, fromX, fromY, toX, toY;
16236         char promoChar;
16237         ChessMove moveType;
16238
16239         // [HGM] FENedit
16240         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16241                 EditPositionPasteFEN(move);
16242                 return;
16243         }
16244         // [HGM] movenum: allow move number to be typed in any mode
16245         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16246           ToNrEvent(2*n-1);
16247           return;
16248         }
16249         // undocumented kludge: allow command-line option to be typed in!
16250         // (potentially fatal, and does not implement the effect of the option.)
16251         // should only be used for options that are values on which future decisions will be made,
16252         // and definitely not on options that would be used during initialization.
16253         if(strstr(move, "!!! -") == move) {
16254             ParseArgsFromString(move+4);
16255             return;
16256         }
16257
16258       if (gameMode != EditGame && currentMove != forwardMostMove &&
16259         gameMode != Training) {
16260         DisplayMoveError(_("Displayed move is not current"));
16261       } else {
16262         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16263           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16264         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16265         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16266           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16267           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16268         } else {
16269           DisplayMoveError(_("Could not parse move"));
16270         }
16271       }
16272 }
16273
16274 void
16275 DisplayMove (int moveNumber)
16276 {
16277     char message[MSG_SIZ];
16278     char res[MSG_SIZ];
16279     char cpThinkOutput[MSG_SIZ];
16280
16281     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16282
16283     if (moveNumber == forwardMostMove - 1 ||
16284         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16285
16286         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16287
16288         if (strchr(cpThinkOutput, '\n')) {
16289             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16290         }
16291     } else {
16292         *cpThinkOutput = NULLCHAR;
16293     }
16294
16295     /* [AS] Hide thinking from human user */
16296     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16297         *cpThinkOutput = NULLCHAR;
16298         if( thinkOutput[0] != NULLCHAR ) {
16299             int i;
16300
16301             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16302                 cpThinkOutput[i] = '.';
16303             }
16304             cpThinkOutput[i] = NULLCHAR;
16305             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16306         }
16307     }
16308
16309     if (moveNumber == forwardMostMove - 1 &&
16310         gameInfo.resultDetails != NULL) {
16311         if (gameInfo.resultDetails[0] == NULLCHAR) {
16312           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16313         } else {
16314           snprintf(res, MSG_SIZ, " {%s} %s",
16315                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16316         }
16317     } else {
16318         res[0] = NULLCHAR;
16319     }
16320
16321     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16322         DisplayMessage(res, cpThinkOutput);
16323     } else {
16324       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16325                 WhiteOnMove(moveNumber) ? " " : ".. ",
16326                 parseList[moveNumber], res);
16327         DisplayMessage(message, cpThinkOutput);
16328     }
16329 }
16330
16331 void
16332 DisplayComment (int moveNumber, char *text)
16333 {
16334     char title[MSG_SIZ];
16335
16336     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16337       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16338     } else {
16339       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16340               WhiteOnMove(moveNumber) ? " " : ".. ",
16341               parseList[moveNumber]);
16342     }
16343     if (text != NULL && (appData.autoDisplayComment || commentUp))
16344         CommentPopUp(title, text);
16345 }
16346
16347 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16348  * might be busy thinking or pondering.  It can be omitted if your
16349  * gnuchess is configured to stop thinking immediately on any user
16350  * input.  However, that gnuchess feature depends on the FIONREAD
16351  * ioctl, which does not work properly on some flavors of Unix.
16352  */
16353 void
16354 Attention (ChessProgramState *cps)
16355 {
16356 #if ATTENTION
16357     if (!cps->useSigint) return;
16358     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16359     switch (gameMode) {
16360       case MachinePlaysWhite:
16361       case MachinePlaysBlack:
16362       case TwoMachinesPlay:
16363       case IcsPlayingWhite:
16364       case IcsPlayingBlack:
16365       case AnalyzeMode:
16366       case AnalyzeFile:
16367         /* Skip if we know it isn't thinking */
16368         if (!cps->maybeThinking) return;
16369         if (appData.debugMode)
16370           fprintf(debugFP, "Interrupting %s\n", cps->which);
16371         InterruptChildProcess(cps->pr);
16372         cps->maybeThinking = FALSE;
16373         break;
16374       default:
16375         break;
16376     }
16377 #endif /*ATTENTION*/
16378 }
16379
16380 int
16381 CheckFlags ()
16382 {
16383     if (whiteTimeRemaining <= 0) {
16384         if (!whiteFlag) {
16385             whiteFlag = TRUE;
16386             if (appData.icsActive) {
16387                 if (appData.autoCallFlag &&
16388                     gameMode == IcsPlayingBlack && !blackFlag) {
16389                   SendToICS(ics_prefix);
16390                   SendToICS("flag\n");
16391                 }
16392             } else {
16393                 if (blackFlag) {
16394                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16395                 } else {
16396                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16397                     if (appData.autoCallFlag) {
16398                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16399                         return TRUE;
16400                     }
16401                 }
16402             }
16403         }
16404     }
16405     if (blackTimeRemaining <= 0) {
16406         if (!blackFlag) {
16407             blackFlag = TRUE;
16408             if (appData.icsActive) {
16409                 if (appData.autoCallFlag &&
16410                     gameMode == IcsPlayingWhite && !whiteFlag) {
16411                   SendToICS(ics_prefix);
16412                   SendToICS("flag\n");
16413                 }
16414             } else {
16415                 if (whiteFlag) {
16416                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16417                 } else {
16418                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16419                     if (appData.autoCallFlag) {
16420                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16421                         return TRUE;
16422                     }
16423                 }
16424             }
16425         }
16426     }
16427     return FALSE;
16428 }
16429
16430 void
16431 CheckTimeControl ()
16432 {
16433     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16434         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16435
16436     /*
16437      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16438      */
16439     if ( !WhiteOnMove(forwardMostMove) ) {
16440         /* White made time control */
16441         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16442         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16443         /* [HGM] time odds: correct new time quota for time odds! */
16444                                             / WhitePlayer()->timeOdds;
16445         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16446     } else {
16447         lastBlack -= blackTimeRemaining;
16448         /* Black made time control */
16449         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16450                                             / WhitePlayer()->other->timeOdds;
16451         lastWhite = whiteTimeRemaining;
16452     }
16453 }
16454
16455 void
16456 DisplayBothClocks ()
16457 {
16458     int wom = gameMode == EditPosition ?
16459       !blackPlaysFirst : WhiteOnMove(currentMove);
16460     DisplayWhiteClock(whiteTimeRemaining, wom);
16461     DisplayBlackClock(blackTimeRemaining, !wom);
16462 }
16463
16464
16465 /* Timekeeping seems to be a portability nightmare.  I think everyone
16466    has ftime(), but I'm really not sure, so I'm including some ifdefs
16467    to use other calls if you don't.  Clocks will be less accurate if
16468    you have neither ftime nor gettimeofday.
16469 */
16470
16471 /* VS 2008 requires the #include outside of the function */
16472 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16473 #include <sys/timeb.h>
16474 #endif
16475
16476 /* Get the current time as a TimeMark */
16477 void
16478 GetTimeMark (TimeMark *tm)
16479 {
16480 #if HAVE_GETTIMEOFDAY
16481
16482     struct timeval timeVal;
16483     struct timezone timeZone;
16484
16485     gettimeofday(&timeVal, &timeZone);
16486     tm->sec = (long) timeVal.tv_sec;
16487     tm->ms = (int) (timeVal.tv_usec / 1000L);
16488
16489 #else /*!HAVE_GETTIMEOFDAY*/
16490 #if HAVE_FTIME
16491
16492 // include <sys/timeb.h> / moved to just above start of function
16493     struct timeb timeB;
16494
16495     ftime(&timeB);
16496     tm->sec = (long) timeB.time;
16497     tm->ms = (int) timeB.millitm;
16498
16499 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16500     tm->sec = (long) time(NULL);
16501     tm->ms = 0;
16502 #endif
16503 #endif
16504 }
16505
16506 /* Return the difference in milliseconds between two
16507    time marks.  We assume the difference will fit in a long!
16508 */
16509 long
16510 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16511 {
16512     return 1000L*(tm2->sec - tm1->sec) +
16513            (long) (tm2->ms - tm1->ms);
16514 }
16515
16516
16517 /*
16518  * Code to manage the game clocks.
16519  *
16520  * In tournament play, black starts the clock and then white makes a move.
16521  * We give the human user a slight advantage if he is playing white---the
16522  * clocks don't run until he makes his first move, so it takes zero time.
16523  * Also, we don't account for network lag, so we could get out of sync
16524  * with GNU Chess's clock -- but then, referees are always right.
16525  */
16526
16527 static TimeMark tickStartTM;
16528 static long intendedTickLength;
16529
16530 long
16531 NextTickLength (long timeRemaining)
16532 {
16533     long nominalTickLength, nextTickLength;
16534
16535     if (timeRemaining > 0L && timeRemaining <= 10000L)
16536       nominalTickLength = 100L;
16537     else
16538       nominalTickLength = 1000L;
16539     nextTickLength = timeRemaining % nominalTickLength;
16540     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16541
16542     return nextTickLength;
16543 }
16544
16545 /* Adjust clock one minute up or down */
16546 void
16547 AdjustClock (Boolean which, int dir)
16548 {
16549     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16550     if(which) blackTimeRemaining += 60000*dir;
16551     else      whiteTimeRemaining += 60000*dir;
16552     DisplayBothClocks();
16553     adjustedClock = TRUE;
16554 }
16555
16556 /* Stop clocks and reset to a fresh time control */
16557 void
16558 ResetClocks ()
16559 {
16560     (void) StopClockTimer();
16561     if (appData.icsActive) {
16562         whiteTimeRemaining = blackTimeRemaining = 0;
16563     } else if (searchTime) {
16564         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16565         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16566     } else { /* [HGM] correct new time quote for time odds */
16567         whiteTC = blackTC = fullTimeControlString;
16568         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16569         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16570     }
16571     if (whiteFlag || blackFlag) {
16572         DisplayTitle("");
16573         whiteFlag = blackFlag = FALSE;
16574     }
16575     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16576     DisplayBothClocks();
16577     adjustedClock = FALSE;
16578 }
16579
16580 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16581
16582 /* Decrement running clock by amount of time that has passed */
16583 void
16584 DecrementClocks ()
16585 {
16586     long timeRemaining;
16587     long lastTickLength, fudge;
16588     TimeMark now;
16589
16590     if (!appData.clockMode) return;
16591     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16592
16593     GetTimeMark(&now);
16594
16595     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16596
16597     /* Fudge if we woke up a little too soon */
16598     fudge = intendedTickLength - lastTickLength;
16599     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16600
16601     if (WhiteOnMove(forwardMostMove)) {
16602         if(whiteNPS >= 0) lastTickLength = 0;
16603         timeRemaining = whiteTimeRemaining -= lastTickLength;
16604         if(timeRemaining < 0 && !appData.icsActive) {
16605             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16606             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16607                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16608                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16609             }
16610         }
16611         DisplayWhiteClock(whiteTimeRemaining - fudge,
16612                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16613     } else {
16614         if(blackNPS >= 0) lastTickLength = 0;
16615         timeRemaining = blackTimeRemaining -= lastTickLength;
16616         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16617             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16618             if(suddenDeath) {
16619                 blackStartMove = forwardMostMove;
16620                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16621             }
16622         }
16623         DisplayBlackClock(blackTimeRemaining - fudge,
16624                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16625     }
16626     if (CheckFlags()) return;
16627
16628     if(twoBoards) { // count down secondary board's clocks as well
16629         activePartnerTime -= lastTickLength;
16630         partnerUp = 1;
16631         if(activePartner == 'W')
16632             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16633         else
16634             DisplayBlackClock(activePartnerTime, TRUE);
16635         partnerUp = 0;
16636     }
16637
16638     tickStartTM = now;
16639     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16640     StartClockTimer(intendedTickLength);
16641
16642     /* if the time remaining has fallen below the alarm threshold, sound the
16643      * alarm. if the alarm has sounded and (due to a takeback or time control
16644      * with increment) the time remaining has increased to a level above the
16645      * threshold, reset the alarm so it can sound again.
16646      */
16647
16648     if (appData.icsActive && appData.icsAlarm) {
16649
16650         /* make sure we are dealing with the user's clock */
16651         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16652                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16653            )) return;
16654
16655         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16656             alarmSounded = FALSE;
16657         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16658             PlayAlarmSound();
16659             alarmSounded = TRUE;
16660         }
16661     }
16662 }
16663
16664
16665 /* A player has just moved, so stop the previously running
16666    clock and (if in clock mode) start the other one.
16667    We redisplay both clocks in case we're in ICS mode, because
16668    ICS gives us an update to both clocks after every move.
16669    Note that this routine is called *after* forwardMostMove
16670    is updated, so the last fractional tick must be subtracted
16671    from the color that is *not* on move now.
16672 */
16673 void
16674 SwitchClocks (int newMoveNr)
16675 {
16676     long lastTickLength;
16677     TimeMark now;
16678     int flagged = FALSE;
16679
16680     GetTimeMark(&now);
16681
16682     if (StopClockTimer() && appData.clockMode) {
16683         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16684         if (!WhiteOnMove(forwardMostMove)) {
16685             if(blackNPS >= 0) lastTickLength = 0;
16686             blackTimeRemaining -= lastTickLength;
16687            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16688 //         if(pvInfoList[forwardMostMove].time == -1)
16689                  pvInfoList[forwardMostMove].time =               // use GUI time
16690                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16691         } else {
16692            if(whiteNPS >= 0) lastTickLength = 0;
16693            whiteTimeRemaining -= lastTickLength;
16694            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16695 //         if(pvInfoList[forwardMostMove].time == -1)
16696                  pvInfoList[forwardMostMove].time =
16697                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16698         }
16699         flagged = CheckFlags();
16700     }
16701     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16702     CheckTimeControl();
16703
16704     if (flagged || !appData.clockMode) return;
16705
16706     switch (gameMode) {
16707       case MachinePlaysBlack:
16708       case MachinePlaysWhite:
16709       case BeginningOfGame:
16710         if (pausing) return;
16711         break;
16712
16713       case EditGame:
16714       case PlayFromGameFile:
16715       case IcsExamining:
16716         return;
16717
16718       default:
16719         break;
16720     }
16721
16722     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16723         if(WhiteOnMove(forwardMostMove))
16724              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16725         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16726     }
16727
16728     tickStartTM = now;
16729     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16730       whiteTimeRemaining : blackTimeRemaining);
16731     StartClockTimer(intendedTickLength);
16732 }
16733
16734
16735 /* Stop both clocks */
16736 void
16737 StopClocks ()
16738 {
16739     long lastTickLength;
16740     TimeMark now;
16741
16742     if (!StopClockTimer()) return;
16743     if (!appData.clockMode) return;
16744
16745     GetTimeMark(&now);
16746
16747     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16748     if (WhiteOnMove(forwardMostMove)) {
16749         if(whiteNPS >= 0) lastTickLength = 0;
16750         whiteTimeRemaining -= lastTickLength;
16751         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16752     } else {
16753         if(blackNPS >= 0) lastTickLength = 0;
16754         blackTimeRemaining -= lastTickLength;
16755         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16756     }
16757     CheckFlags();
16758 }
16759
16760 /* Start clock of player on move.  Time may have been reset, so
16761    if clock is already running, stop and restart it. */
16762 void
16763 StartClocks ()
16764 {
16765     (void) StopClockTimer(); /* in case it was running already */
16766     DisplayBothClocks();
16767     if (CheckFlags()) return;
16768
16769     if (!appData.clockMode) return;
16770     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16771
16772     GetTimeMark(&tickStartTM);
16773     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16774       whiteTimeRemaining : blackTimeRemaining);
16775
16776    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16777     whiteNPS = blackNPS = -1;
16778     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16779        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16780         whiteNPS = first.nps;
16781     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16782        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16783         blackNPS = first.nps;
16784     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16785         whiteNPS = second.nps;
16786     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16787         blackNPS = second.nps;
16788     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16789
16790     StartClockTimer(intendedTickLength);
16791 }
16792
16793 char *
16794 TimeString (long ms)
16795 {
16796     long second, minute, hour, day;
16797     char *sign = "";
16798     static char buf[32];
16799
16800     if (ms > 0 && ms <= 9900) {
16801       /* convert milliseconds to tenths, rounding up */
16802       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16803
16804       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16805       return buf;
16806     }
16807
16808     /* convert milliseconds to seconds, rounding up */
16809     /* use floating point to avoid strangeness of integer division
16810        with negative dividends on many machines */
16811     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16812
16813     if (second < 0) {
16814         sign = "-";
16815         second = -second;
16816     }
16817
16818     day = second / (60 * 60 * 24);
16819     second = second % (60 * 60 * 24);
16820     hour = second / (60 * 60);
16821     second = second % (60 * 60);
16822     minute = second / 60;
16823     second = second % 60;
16824
16825     if (day > 0)
16826       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16827               sign, day, hour, minute, second);
16828     else if (hour > 0)
16829       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16830     else
16831       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16832
16833     return buf;
16834 }
16835
16836
16837 /*
16838  * This is necessary because some C libraries aren't ANSI C compliant yet.
16839  */
16840 char *
16841 StrStr (char *string, char *match)
16842 {
16843     int i, length;
16844
16845     length = strlen(match);
16846
16847     for (i = strlen(string) - length; i >= 0; i--, string++)
16848       if (!strncmp(match, string, length))
16849         return string;
16850
16851     return NULL;
16852 }
16853
16854 char *
16855 StrCaseStr (char *string, char *match)
16856 {
16857     int i, j, length;
16858
16859     length = strlen(match);
16860
16861     for (i = strlen(string) - length; i >= 0; i--, string++) {
16862         for (j = 0; j < length; j++) {
16863             if (ToLower(match[j]) != ToLower(string[j]))
16864               break;
16865         }
16866         if (j == length) return string;
16867     }
16868
16869     return NULL;
16870 }
16871
16872 #ifndef _amigados
16873 int
16874 StrCaseCmp (char *s1, char *s2)
16875 {
16876     char c1, c2;
16877
16878     for (;;) {
16879         c1 = ToLower(*s1++);
16880         c2 = ToLower(*s2++);
16881         if (c1 > c2) return 1;
16882         if (c1 < c2) return -1;
16883         if (c1 == NULLCHAR) return 0;
16884     }
16885 }
16886
16887
16888 int
16889 ToLower (int c)
16890 {
16891     return isupper(c) ? tolower(c) : c;
16892 }
16893
16894
16895 int
16896 ToUpper (int c)
16897 {
16898     return islower(c) ? toupper(c) : c;
16899 }
16900 #endif /* !_amigados    */
16901
16902 char *
16903 StrSave (char *s)
16904 {
16905   char *ret;
16906
16907   if ((ret = (char *) malloc(strlen(s) + 1)))
16908     {
16909       safeStrCpy(ret, s, strlen(s)+1);
16910     }
16911   return ret;
16912 }
16913
16914 char *
16915 StrSavePtr (char *s, char **savePtr)
16916 {
16917     if (*savePtr) {
16918         free(*savePtr);
16919     }
16920     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16921       safeStrCpy(*savePtr, s, strlen(s)+1);
16922     }
16923     return(*savePtr);
16924 }
16925
16926 char *
16927 PGNDate ()
16928 {
16929     time_t clock;
16930     struct tm *tm;
16931     char buf[MSG_SIZ];
16932
16933     clock = time((time_t *)NULL);
16934     tm = localtime(&clock);
16935     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16936             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16937     return StrSave(buf);
16938 }
16939
16940
16941 char *
16942 PositionToFEN (int move, char *overrideCastling)
16943 {
16944     int i, j, fromX, fromY, toX, toY;
16945     int whiteToPlay;
16946     char buf[MSG_SIZ];
16947     char *p, *q;
16948     int emptycount;
16949     ChessSquare piece;
16950
16951     whiteToPlay = (gameMode == EditPosition) ?
16952       !blackPlaysFirst : (move % 2 == 0);
16953     p = buf;
16954
16955     /* Piece placement data */
16956     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16957         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16958         emptycount = 0;
16959         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16960             if (boards[move][i][j] == EmptySquare) {
16961                 emptycount++;
16962             } else { ChessSquare piece = boards[move][i][j];
16963                 if (emptycount > 0) {
16964                     if(emptycount<10) /* [HGM] can be >= 10 */
16965                         *p++ = '0' + emptycount;
16966                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16967                     emptycount = 0;
16968                 }
16969                 if(PieceToChar(piece) == '+') {
16970                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16971                     *p++ = '+';
16972                     piece = (ChessSquare)(DEMOTED piece);
16973                 }
16974                 *p++ = PieceToChar(piece);
16975                 if(p[-1] == '~') {
16976                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16977                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16978                     *p++ = '~';
16979                 }
16980             }
16981         }
16982         if (emptycount > 0) {
16983             if(emptycount<10) /* [HGM] can be >= 10 */
16984                 *p++ = '0' + emptycount;
16985             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16986             emptycount = 0;
16987         }
16988         *p++ = '/';
16989     }
16990     *(p - 1) = ' ';
16991
16992     /* [HGM] print Crazyhouse or Shogi holdings */
16993     if( gameInfo.holdingsWidth ) {
16994         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16995         q = p;
16996         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16997             piece = boards[move][i][BOARD_WIDTH-1];
16998             if( piece != EmptySquare )
16999               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17000                   *p++ = PieceToChar(piece);
17001         }
17002         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17003             piece = boards[move][BOARD_HEIGHT-i-1][0];
17004             if( piece != EmptySquare )
17005               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17006                   *p++ = PieceToChar(piece);
17007         }
17008
17009         if( q == p ) *p++ = '-';
17010         *p++ = ']';
17011         *p++ = ' ';
17012     }
17013
17014     /* Active color */
17015     *p++ = whiteToPlay ? 'w' : 'b';
17016     *p++ = ' ';
17017
17018   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17019     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17020   } else {
17021   if(nrCastlingRights) {
17022      q = p;
17023      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17024        /* [HGM] write directly from rights */
17025            if(boards[move][CASTLING][2] != NoRights &&
17026               boards[move][CASTLING][0] != NoRights   )
17027                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17028            if(boards[move][CASTLING][2] != NoRights &&
17029               boards[move][CASTLING][1] != NoRights   )
17030                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17031            if(boards[move][CASTLING][5] != NoRights &&
17032               boards[move][CASTLING][3] != NoRights   )
17033                 *p++ = boards[move][CASTLING][3] + AAA;
17034            if(boards[move][CASTLING][5] != NoRights &&
17035               boards[move][CASTLING][4] != NoRights   )
17036                 *p++ = boards[move][CASTLING][4] + AAA;
17037      } else {
17038
17039         /* [HGM] write true castling rights */
17040         if( nrCastlingRights == 6 ) {
17041             int q, k=0;
17042             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17043                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17044             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17045                  boards[move][CASTLING][2] != NoRights  );
17046             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17047                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17048                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17049                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17050                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17051             }
17052             if(q) *p++ = 'Q';
17053             k = 0;
17054             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17055                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17056             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17057                  boards[move][CASTLING][5] != NoRights  );
17058             if(gameInfo.variant == VariantSChess) {
17059                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17060                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17061                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17062                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17063             }
17064             if(q) *p++ = 'q';
17065         }
17066      }
17067      if (q == p) *p++ = '-'; /* No castling rights */
17068      *p++ = ' ';
17069   }
17070
17071   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17072      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17073     /* En passant target square */
17074     if (move > backwardMostMove) {
17075         fromX = moveList[move - 1][0] - AAA;
17076         fromY = moveList[move - 1][1] - ONE;
17077         toX = moveList[move - 1][2] - AAA;
17078         toY = moveList[move - 1][3] - ONE;
17079         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17080             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17081             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17082             fromX == toX) {
17083             /* 2-square pawn move just happened */
17084             *p++ = toX + AAA;
17085             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17086         } else {
17087             *p++ = '-';
17088         }
17089     } else if(move == backwardMostMove) {
17090         // [HGM] perhaps we should always do it like this, and forget the above?
17091         if((signed char)boards[move][EP_STATUS] >= 0) {
17092             *p++ = boards[move][EP_STATUS] + AAA;
17093             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17094         } else {
17095             *p++ = '-';
17096         }
17097     } else {
17098         *p++ = '-';
17099     }
17100     *p++ = ' ';
17101   }
17102   }
17103
17104     /* [HGM] find reversible plies */
17105     {   int i = 0, j=move;
17106
17107         if (appData.debugMode) { int k;
17108             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17109             for(k=backwardMostMove; k<=forwardMostMove; k++)
17110                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17111
17112         }
17113
17114         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17115         if( j == backwardMostMove ) i += initialRulePlies;
17116         sprintf(p, "%d ", i);
17117         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17118     }
17119     /* Fullmove number */
17120     sprintf(p, "%d", (move / 2) + 1);
17121
17122     return StrSave(buf);
17123 }
17124
17125 Boolean
17126 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17127 {
17128     int i, j;
17129     char *p, c;
17130     int emptycount, virgin[BOARD_FILES];
17131     ChessSquare piece;
17132
17133     p = fen;
17134
17135     /* [HGM] by default clear Crazyhouse holdings, if present */
17136     if(gameInfo.holdingsWidth) {
17137        for(i=0; i<BOARD_HEIGHT; i++) {
17138            board[i][0]             = EmptySquare; /* black holdings */
17139            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17140            board[i][1]             = (ChessSquare) 0; /* black counts */
17141            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17142        }
17143     }
17144
17145     /* Piece placement data */
17146     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17147         j = 0;
17148         for (;;) {
17149             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17150                 if (*p == '/') p++;
17151                 emptycount = gameInfo.boardWidth - j;
17152                 while (emptycount--)
17153                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17154                 break;
17155 #if(BOARD_FILES >= 10)
17156             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17157                 p++; emptycount=10;
17158                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17159                 while (emptycount--)
17160                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17161 #endif
17162             } else if (isdigit(*p)) {
17163                 emptycount = *p++ - '0';
17164                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17165                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17166                 while (emptycount--)
17167                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17168             } else if (*p == '+' || isalpha(*p)) {
17169                 if (j >= gameInfo.boardWidth) return FALSE;
17170                 if(*p=='+') {
17171                     piece = CharToPiece(*++p);
17172                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17173                     piece = (ChessSquare) (PROMOTED piece ); p++;
17174                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17175                 } else piece = CharToPiece(*p++);
17176
17177                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17178                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17179                     piece = (ChessSquare) (PROMOTED piece);
17180                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17181                     p++;
17182                 }
17183                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17184             } else {
17185                 return FALSE;
17186             }
17187         }
17188     }
17189     while (*p == '/' || *p == ' ') p++;
17190
17191     /* [HGM] look for Crazyhouse holdings here */
17192     while(*p==' ') p++;
17193     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17194         if(*p == '[') p++;
17195         if(*p == '-' ) p++; /* empty holdings */ else {
17196             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17197             /* if we would allow FEN reading to set board size, we would   */
17198             /* have to add holdings and shift the board read so far here   */
17199             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17200                 p++;
17201                 if((int) piece >= (int) BlackPawn ) {
17202                     i = (int)piece - (int)BlackPawn;
17203                     i = PieceToNumber((ChessSquare)i);
17204                     if( i >= gameInfo.holdingsSize ) return FALSE;
17205                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17206                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17207                 } else {
17208                     i = (int)piece - (int)WhitePawn;
17209                     i = PieceToNumber((ChessSquare)i);
17210                     if( i >= gameInfo.holdingsSize ) return FALSE;
17211                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17212                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17213                 }
17214             }
17215         }
17216         if(*p == ']') p++;
17217     }
17218
17219     while(*p == ' ') p++;
17220
17221     /* Active color */
17222     c = *p++;
17223     if(appData.colorNickNames) {
17224       if( c == appData.colorNickNames[0] ) c = 'w'; else
17225       if( c == appData.colorNickNames[1] ) c = 'b';
17226     }
17227     switch (c) {
17228       case 'w':
17229         *blackPlaysFirst = FALSE;
17230         break;
17231       case 'b':
17232         *blackPlaysFirst = TRUE;
17233         break;
17234       default:
17235         return FALSE;
17236     }
17237
17238     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17239     /* return the extra info in global variiables             */
17240
17241     /* set defaults in case FEN is incomplete */
17242     board[EP_STATUS] = EP_UNKNOWN;
17243     for(i=0; i<nrCastlingRights; i++ ) {
17244         board[CASTLING][i] =
17245             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17246     }   /* assume possible unless obviously impossible */
17247     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17248     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17249     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17250                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17251     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17252     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17253     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17254                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17255     FENrulePlies = 0;
17256
17257     while(*p==' ') p++;
17258     if(nrCastlingRights) {
17259       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17260       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17261           /* castling indicator present, so default becomes no castlings */
17262           for(i=0; i<nrCastlingRights; i++ ) {
17263                  board[CASTLING][i] = NoRights;
17264           }
17265       }
17266       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17267              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17268              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17269              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17270         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17271
17272         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17273             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17274             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17275         }
17276         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17277             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17278         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17279                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17280         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17281                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17282         switch(c) {
17283           case'K':
17284               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17285               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17286               board[CASTLING][2] = whiteKingFile;
17287               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17288               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17289               break;
17290           case'Q':
17291               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17292               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17293               board[CASTLING][2] = whiteKingFile;
17294               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17295               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17296               break;
17297           case'k':
17298               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17299               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17300               board[CASTLING][5] = blackKingFile;
17301               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17302               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17303               break;
17304           case'q':
17305               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17306               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17307               board[CASTLING][5] = blackKingFile;
17308               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17309               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17310           case '-':
17311               break;
17312           default: /* FRC castlings */
17313               if(c >= 'a') { /* black rights */
17314                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17315                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17316                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17317                   if(i == BOARD_RGHT) break;
17318                   board[CASTLING][5] = i;
17319                   c -= AAA;
17320                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17321                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17322                   if(c > i)
17323                       board[CASTLING][3] = c;
17324                   else
17325                       board[CASTLING][4] = c;
17326               } else { /* white rights */
17327                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17328                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17329                     if(board[0][i] == WhiteKing) break;
17330                   if(i == BOARD_RGHT) break;
17331                   board[CASTLING][2] = i;
17332                   c -= AAA - 'a' + 'A';
17333                   if(board[0][c] >= WhiteKing) break;
17334                   if(c > i)
17335                       board[CASTLING][0] = c;
17336                   else
17337                       board[CASTLING][1] = c;
17338               }
17339         }
17340       }
17341       for(i=0; i<nrCastlingRights; i++)
17342         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17343       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17344     if (appData.debugMode) {
17345         fprintf(debugFP, "FEN castling rights:");
17346         for(i=0; i<nrCastlingRights; i++)
17347         fprintf(debugFP, " %d", board[CASTLING][i]);
17348         fprintf(debugFP, "\n");
17349     }
17350
17351       while(*p==' ') p++;
17352     }
17353
17354     /* read e.p. field in games that know e.p. capture */
17355     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17356        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17357       if(*p=='-') {
17358         p++; board[EP_STATUS] = EP_NONE;
17359       } else {
17360          char c = *p++ - AAA;
17361
17362          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17363          if(*p >= '0' && *p <='9') p++;
17364          board[EP_STATUS] = c;
17365       }
17366     }
17367
17368
17369     if(sscanf(p, "%d", &i) == 1) {
17370         FENrulePlies = i; /* 50-move ply counter */
17371         /* (The move number is still ignored)    */
17372     }
17373
17374     return TRUE;
17375 }
17376
17377 void
17378 EditPositionPasteFEN (char *fen)
17379 {
17380   if (fen != NULL) {
17381     Board initial_position;
17382
17383     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17384       DisplayError(_("Bad FEN position in clipboard"), 0);
17385       return ;
17386     } else {
17387       int savedBlackPlaysFirst = blackPlaysFirst;
17388       EditPositionEvent();
17389       blackPlaysFirst = savedBlackPlaysFirst;
17390       CopyBoard(boards[0], initial_position);
17391       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17392       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17393       DisplayBothClocks();
17394       DrawPosition(FALSE, boards[currentMove]);
17395     }
17396   }
17397 }
17398
17399 static char cseq[12] = "\\   ";
17400
17401 Boolean
17402 set_cont_sequence (char *new_seq)
17403 {
17404     int len;
17405     Boolean ret;
17406
17407     // handle bad attempts to set the sequence
17408         if (!new_seq)
17409                 return 0; // acceptable error - no debug
17410
17411     len = strlen(new_seq);
17412     ret = (len > 0) && (len < sizeof(cseq));
17413     if (ret)
17414       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17415     else if (appData.debugMode)
17416       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17417     return ret;
17418 }
17419
17420 /*
17421     reformat a source message so words don't cross the width boundary.  internal
17422     newlines are not removed.  returns the wrapped size (no null character unless
17423     included in source message).  If dest is NULL, only calculate the size required
17424     for the dest buffer.  lp argument indicats line position upon entry, and it's
17425     passed back upon exit.
17426 */
17427 int
17428 wrap (char *dest, char *src, int count, int width, int *lp)
17429 {
17430     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17431
17432     cseq_len = strlen(cseq);
17433     old_line = line = *lp;
17434     ansi = len = clen = 0;
17435
17436     for (i=0; i < count; i++)
17437     {
17438         if (src[i] == '\033')
17439             ansi = 1;
17440
17441         // if we hit the width, back up
17442         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17443         {
17444             // store i & len in case the word is too long
17445             old_i = i, old_len = len;
17446
17447             // find the end of the last word
17448             while (i && src[i] != ' ' && src[i] != '\n')
17449             {
17450                 i--;
17451                 len--;
17452             }
17453
17454             // word too long?  restore i & len before splitting it
17455             if ((old_i-i+clen) >= width)
17456             {
17457                 i = old_i;
17458                 len = old_len;
17459             }
17460
17461             // extra space?
17462             if (i && src[i-1] == ' ')
17463                 len--;
17464
17465             if (src[i] != ' ' && src[i] != '\n')
17466             {
17467                 i--;
17468                 if (len)
17469                     len--;
17470             }
17471
17472             // now append the newline and continuation sequence
17473             if (dest)
17474                 dest[len] = '\n';
17475             len++;
17476             if (dest)
17477                 strncpy(dest+len, cseq, cseq_len);
17478             len += cseq_len;
17479             line = cseq_len;
17480             clen = cseq_len;
17481             continue;
17482         }
17483
17484         if (dest)
17485             dest[len] = src[i];
17486         len++;
17487         if (!ansi)
17488             line++;
17489         if (src[i] == '\n')
17490             line = 0;
17491         if (src[i] == 'm')
17492             ansi = 0;
17493     }
17494     if (dest && appData.debugMode)
17495     {
17496         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17497             count, width, line, len, *lp);
17498         show_bytes(debugFP, src, count);
17499         fprintf(debugFP, "\ndest: ");
17500         show_bytes(debugFP, dest, len);
17501         fprintf(debugFP, "\n");
17502     }
17503     *lp = dest ? line : old_line;
17504
17505     return len;
17506 }
17507
17508 // [HGM] vari: routines for shelving variations
17509 Boolean modeRestore = FALSE;
17510
17511 void
17512 PushInner (int firstMove, int lastMove)
17513 {
17514         int i, j, nrMoves = lastMove - firstMove;
17515
17516         // push current tail of game on stack
17517         savedResult[storedGames] = gameInfo.result;
17518         savedDetails[storedGames] = gameInfo.resultDetails;
17519         gameInfo.resultDetails = NULL;
17520         savedFirst[storedGames] = firstMove;
17521         savedLast [storedGames] = lastMove;
17522         savedFramePtr[storedGames] = framePtr;
17523         framePtr -= nrMoves; // reserve space for the boards
17524         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17525             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17526             for(j=0; j<MOVE_LEN; j++)
17527                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17528             for(j=0; j<2*MOVE_LEN; j++)
17529                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17530             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17531             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17532             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17533             pvInfoList[firstMove+i-1].depth = 0;
17534             commentList[framePtr+i] = commentList[firstMove+i];
17535             commentList[firstMove+i] = NULL;
17536         }
17537
17538         storedGames++;
17539         forwardMostMove = firstMove; // truncate game so we can start variation
17540 }
17541
17542 void
17543 PushTail (int firstMove, int lastMove)
17544 {
17545         if(appData.icsActive) { // only in local mode
17546                 forwardMostMove = currentMove; // mimic old ICS behavior
17547                 return;
17548         }
17549         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17550
17551         PushInner(firstMove, lastMove);
17552         if(storedGames == 1) GreyRevert(FALSE);
17553         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17554 }
17555
17556 void
17557 PopInner (Boolean annotate)
17558 {
17559         int i, j, nrMoves;
17560         char buf[8000], moveBuf[20];
17561
17562         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17563         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17564         nrMoves = savedLast[storedGames] - currentMove;
17565         if(annotate) {
17566                 int cnt = 10;
17567                 if(!WhiteOnMove(currentMove))
17568                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17569                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17570                 for(i=currentMove; i<forwardMostMove; i++) {
17571                         if(WhiteOnMove(i))
17572                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17573                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17574                         strcat(buf, moveBuf);
17575                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17576                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17577                 }
17578                 strcat(buf, ")");
17579         }
17580         for(i=1; i<=nrMoves; i++) { // copy last variation back
17581             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17582             for(j=0; j<MOVE_LEN; j++)
17583                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17584             for(j=0; j<2*MOVE_LEN; j++)
17585                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17586             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17587             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17588             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17589             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17590             commentList[currentMove+i] = commentList[framePtr+i];
17591             commentList[framePtr+i] = NULL;
17592         }
17593         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17594         framePtr = savedFramePtr[storedGames];
17595         gameInfo.result = savedResult[storedGames];
17596         if(gameInfo.resultDetails != NULL) {
17597             free(gameInfo.resultDetails);
17598       }
17599         gameInfo.resultDetails = savedDetails[storedGames];
17600         forwardMostMove = currentMove + nrMoves;
17601 }
17602
17603 Boolean
17604 PopTail (Boolean annotate)
17605 {
17606         if(appData.icsActive) return FALSE; // only in local mode
17607         if(!storedGames) return FALSE; // sanity
17608         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17609
17610         PopInner(annotate);
17611         if(currentMove < forwardMostMove) ForwardEvent(); else
17612         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17613
17614         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17615         return TRUE;
17616 }
17617
17618 void
17619 CleanupTail ()
17620 {       // remove all shelved variations
17621         int i;
17622         for(i=0; i<storedGames; i++) {
17623             if(savedDetails[i])
17624                 free(savedDetails[i]);
17625             savedDetails[i] = NULL;
17626         }
17627         for(i=framePtr; i<MAX_MOVES; i++) {
17628                 if(commentList[i]) free(commentList[i]);
17629                 commentList[i] = NULL;
17630         }
17631         framePtr = MAX_MOVES-1;
17632         storedGames = 0;
17633 }
17634
17635 void
17636 LoadVariation (int index, char *text)
17637 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17638         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17639         int level = 0, move;
17640
17641         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17642         // first find outermost bracketing variation
17643         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17644             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17645                 if(*p == '{') wait = '}'; else
17646                 if(*p == '[') wait = ']'; else
17647                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17648                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17649             }
17650             if(*p == wait) wait = NULLCHAR; // closing ]} found
17651             p++;
17652         }
17653         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17654         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17655         end[1] = NULLCHAR; // clip off comment beyond variation
17656         ToNrEvent(currentMove-1);
17657         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17658         // kludge: use ParsePV() to append variation to game
17659         move = currentMove;
17660         ParsePV(start, TRUE, TRUE);
17661         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17662         ClearPremoveHighlights();
17663         CommentPopDown();
17664         ToNrEvent(currentMove+1);
17665 }
17666
17667 void
17668 LoadTheme ()
17669 {
17670     char *p, *q, buf[MSG_SIZ];
17671     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17672         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17673         ParseArgsFromString(buf);
17674         ActivateTheme(TRUE); // also redo colors
17675         return;
17676     }
17677     p = nickName;
17678     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17679     {
17680         int len;
17681         q = appData.themeNames;
17682         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17683       if(appData.useBitmaps) {
17684         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17685                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17686                 appData.liteBackTextureMode,
17687                 appData.darkBackTextureMode );
17688       } else {
17689         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17690                 Col2Text(2),   // lightSquareColor
17691                 Col2Text(3) ); // darkSquareColor
17692       }
17693       if(appData.useBorder) {
17694         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17695                 appData.border);
17696       } else {
17697         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17698       }
17699       if(appData.useFont) {
17700         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17701                 appData.renderPiecesWithFont,
17702                 appData.fontToPieceTable,
17703                 Col2Text(9),    // appData.fontBackColorWhite
17704                 Col2Text(10) ); // appData.fontForeColorBlack
17705       } else {
17706         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17707                 appData.pieceDirectory);
17708         if(!appData.pieceDirectory[0])
17709           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17710                 Col2Text(0),   // whitePieceColor
17711                 Col2Text(1) ); // blackPieceColor
17712       }
17713       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17714                 Col2Text(4),   // highlightSquareColor
17715                 Col2Text(5) ); // premoveHighlightColor
17716         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17717         if(insert != q) insert[-1] = NULLCHAR;
17718         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17719         if(q)   free(q);
17720     }
17721     ActivateTheme(FALSE);
17722 }