f608f6070b7cd4e1ef9ad0465727d97c94248031
[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       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
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(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9457             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9458         board[fromY][fromX] = EmptySquare;
9459     } else if ((fromY >= BOARD_HEIGHT>>1)
9460                && (toX != fromX)
9461                && gameInfo.variant != VariantXiangqi
9462                && gameInfo.variant != VariantBerolina
9463                && (board[fromY][fromX] == WhitePawn)
9464                && (board[toY][toX] == EmptySquare)) {
9465         board[fromY][fromX] = EmptySquare;
9466         board[toY][toX] = WhitePawn;
9467         captured = board[toY - 1][toX];
9468         board[toY - 1][toX] = EmptySquare;
9469     } else if ((fromY == BOARD_HEIGHT-4)
9470                && (toX == fromX)
9471                && gameInfo.variant == VariantBerolina
9472                && (board[fromY][fromX] == WhitePawn)
9473                && (board[toY][toX] == EmptySquare)) {
9474         board[fromY][fromX] = EmptySquare;
9475         board[toY][toX] = WhitePawn;
9476         if(oldEP & EP_BEROLIN_A) {
9477                 captured = board[fromY][fromX-1];
9478                 board[fromY][fromX-1] = EmptySquare;
9479         }else{  captured = board[fromY][fromX+1];
9480                 board[fromY][fromX+1] = EmptySquare;
9481         }
9482     } else if (board[fromY][fromX] == king
9483         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9484                && toY == fromY && toX > fromX+1) {
9485         board[fromY][fromX] = EmptySquare;
9486         board[toY][toX] = king;
9487         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9488         board[fromY][BOARD_RGHT-1] = EmptySquare;
9489     } else if (board[fromY][fromX] == king
9490         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9491                && toY == fromY && toX < fromX-1) {
9492         board[fromY][fromX] = EmptySquare;
9493         board[toY][toX] = king;
9494         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9495         board[fromY][BOARD_LEFT] = EmptySquare;
9496     } else if (fromY == 7 && fromX == 3
9497                && board[fromY][fromX] == BlackKing
9498                && toY == 7 && toX == 5) {
9499         board[fromY][fromX] = EmptySquare;
9500         board[toY][toX] = BlackKing;
9501         board[fromY][7] = EmptySquare;
9502         board[toY][4] = BlackRook;
9503     } else if (fromY == 7 && fromX == 3
9504                && board[fromY][fromX] == BlackKing
9505                && toY == 7 && toX == 1) {
9506         board[fromY][fromX] = EmptySquare;
9507         board[toY][toX] = BlackKing;
9508         board[fromY][0] = EmptySquare;
9509         board[toY][2] = BlackRook;
9510     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9511                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9512                && toY < promoRank && promoChar
9513                ) {
9514         /* black pawn promotion */
9515         board[toY][toX] = CharToPiece(ToLower(promoChar));
9516         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9517             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9518         board[fromY][fromX] = EmptySquare;
9519     } else if ((fromY < BOARD_HEIGHT>>1)
9520                && (toX != fromX)
9521                && gameInfo.variant != VariantXiangqi
9522                && gameInfo.variant != VariantBerolina
9523                && (board[fromY][fromX] == BlackPawn)
9524                && (board[toY][toX] == EmptySquare)) {
9525         board[fromY][fromX] = EmptySquare;
9526         board[toY][toX] = BlackPawn;
9527         captured = board[toY + 1][toX];
9528         board[toY + 1][toX] = EmptySquare;
9529     } else if ((fromY == 3)
9530                && (toX == fromX)
9531                && gameInfo.variant == VariantBerolina
9532                && (board[fromY][fromX] == BlackPawn)
9533                && (board[toY][toX] == EmptySquare)) {
9534         board[fromY][fromX] = EmptySquare;
9535         board[toY][toX] = BlackPawn;
9536         if(oldEP & EP_BEROLIN_A) {
9537                 captured = board[fromY][fromX-1];
9538                 board[fromY][fromX-1] = EmptySquare;
9539         }else{  captured = board[fromY][fromX+1];
9540                 board[fromY][fromX+1] = EmptySquare;
9541         }
9542     } else {
9543         board[toY][toX] = board[fromY][fromX];
9544         board[fromY][fromX] = EmptySquare;
9545     }
9546   }
9547
9548     if (gameInfo.holdingsWidth != 0) {
9549
9550       /* !!A lot more code needs to be written to support holdings  */
9551       /* [HGM] OK, so I have written it. Holdings are stored in the */
9552       /* penultimate board files, so they are automaticlly stored   */
9553       /* in the game history.                                       */
9554       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9555                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9556         /* Delete from holdings, by decreasing count */
9557         /* and erasing image if necessary            */
9558         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9559         if(p < (int) BlackPawn) { /* white drop */
9560              p -= (int)WhitePawn;
9561                  p = PieceToNumber((ChessSquare)p);
9562              if(p >= gameInfo.holdingsSize) p = 0;
9563              if(--board[p][BOARD_WIDTH-2] <= 0)
9564                   board[p][BOARD_WIDTH-1] = EmptySquare;
9565              if((int)board[p][BOARD_WIDTH-2] < 0)
9566                         board[p][BOARD_WIDTH-2] = 0;
9567         } else {                  /* black drop */
9568              p -= (int)BlackPawn;
9569                  p = PieceToNumber((ChessSquare)p);
9570              if(p >= gameInfo.holdingsSize) p = 0;
9571              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9572                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9573              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9574                         board[BOARD_HEIGHT-1-p][1] = 0;
9575         }
9576       }
9577       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9578           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9579         /* [HGM] holdings: Add to holdings, if holdings exist */
9580         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9581                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9582                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9583         }
9584         p = (int) captured;
9585         if (p >= (int) BlackPawn) {
9586           p -= (int)BlackPawn;
9587           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9588                   /* in Shogi restore piece to its original  first */
9589                   captured = (ChessSquare) (DEMOTED captured);
9590                   p = DEMOTED p;
9591           }
9592           p = PieceToNumber((ChessSquare)p);
9593           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9594           board[p][BOARD_WIDTH-2]++;
9595           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9596         } else {
9597           p -= (int)WhitePawn;
9598           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9599                   captured = (ChessSquare) (DEMOTED captured);
9600                   p = DEMOTED p;
9601           }
9602           p = PieceToNumber((ChessSquare)p);
9603           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9604           board[BOARD_HEIGHT-1-p][1]++;
9605           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9606         }
9607       }
9608     } else if (gameInfo.variant == VariantAtomic) {
9609       if (captured != EmptySquare) {
9610         int y, x;
9611         for (y = toY-1; y <= toY+1; y++) {
9612           for (x = toX-1; x <= toX+1; x++) {
9613             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9614                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9615               board[y][x] = EmptySquare;
9616             }
9617           }
9618         }
9619         board[toY][toX] = EmptySquare;
9620       }
9621     }
9622     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9623         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9624     } else
9625     if(promoChar == '+') {
9626         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9627         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9628     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9629         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9630         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9631            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9632         board[toY][toX] = newPiece;
9633     }
9634     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9635                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9636         // [HGM] superchess: take promotion piece out of holdings
9637         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9638         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9639             if(!--board[k][BOARD_WIDTH-2])
9640                 board[k][BOARD_WIDTH-1] = EmptySquare;
9641         } else {
9642             if(!--board[BOARD_HEIGHT-1-k][1])
9643                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9644         }
9645     }
9646
9647 }
9648
9649 /* Updates forwardMostMove */
9650 void
9651 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9652 {
9653 //    forwardMostMove++; // [HGM] bare: moved downstream
9654
9655     (void) CoordsToAlgebraic(boards[forwardMostMove],
9656                              PosFlags(forwardMostMove),
9657                              fromY, fromX, toY, toX, promoChar,
9658                              parseList[forwardMostMove]);
9659
9660     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9661         int timeLeft; static int lastLoadFlag=0; int king, piece;
9662         piece = boards[forwardMostMove][fromY][fromX];
9663         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9664         if(gameInfo.variant == VariantKnightmate)
9665             king += (int) WhiteUnicorn - (int) WhiteKing;
9666         if(forwardMostMove == 0) {
9667             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9668                 fprintf(serverMoves, "%s;", UserName());
9669             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9670                 fprintf(serverMoves, "%s;", second.tidy);
9671             fprintf(serverMoves, "%s;", first.tidy);
9672             if(gameMode == MachinePlaysWhite)
9673                 fprintf(serverMoves, "%s;", UserName());
9674             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9675                 fprintf(serverMoves, "%s;", second.tidy);
9676         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9677         lastLoadFlag = loadFlag;
9678         // print base move
9679         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9680         // print castling suffix
9681         if( toY == fromY && piece == king ) {
9682             if(toX-fromX > 1)
9683                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9684             if(fromX-toX >1)
9685                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9686         }
9687         // e.p. suffix
9688         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9689              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9690              boards[forwardMostMove][toY][toX] == EmptySquare
9691              && fromX != toX && fromY != toY)
9692                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9693         // promotion suffix
9694         if(promoChar != NULLCHAR) {
9695             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9696                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9697                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9698             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9699         }
9700         if(!loadFlag) {
9701                 char buf[MOVE_LEN*2], *p; int len;
9702             fprintf(serverMoves, "/%d/%d",
9703                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9704             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9705             else                      timeLeft = blackTimeRemaining/1000;
9706             fprintf(serverMoves, "/%d", timeLeft);
9707                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9708                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9709                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9710                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9711             fprintf(serverMoves, "/%s", buf);
9712         }
9713         fflush(serverMoves);
9714     }
9715
9716     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9717         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9718       return;
9719     }
9720     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9721     if (commentList[forwardMostMove+1] != NULL) {
9722         free(commentList[forwardMostMove+1]);
9723         commentList[forwardMostMove+1] = NULL;
9724     }
9725     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9726     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9727     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9728     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9729     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9730     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9731     adjustedClock = FALSE;
9732     gameInfo.result = GameUnfinished;
9733     if (gameInfo.resultDetails != NULL) {
9734         free(gameInfo.resultDetails);
9735         gameInfo.resultDetails = NULL;
9736     }
9737     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9738                               moveList[forwardMostMove - 1]);
9739     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9740       case MT_NONE:
9741       case MT_STALEMATE:
9742       default:
9743         break;
9744       case MT_CHECK:
9745         if(gameInfo.variant != VariantShogi)
9746             strcat(parseList[forwardMostMove - 1], "+");
9747         break;
9748       case MT_CHECKMATE:
9749       case MT_STAINMATE:
9750         strcat(parseList[forwardMostMove - 1], "#");
9751         break;
9752     }
9753
9754 }
9755
9756 /* Updates currentMove if not pausing */
9757 void
9758 ShowMove (int fromX, int fromY, int toX, int toY)
9759 {
9760     int instant = (gameMode == PlayFromGameFile) ?
9761         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9762     if(appData.noGUI) return;
9763     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9764         if (!instant) {
9765             if (forwardMostMove == currentMove + 1) {
9766                 AnimateMove(boards[forwardMostMove - 1],
9767                             fromX, fromY, toX, toY);
9768             }
9769         }
9770         currentMove = forwardMostMove;
9771     }
9772
9773     if (instant) return;
9774
9775     DisplayMove(currentMove - 1);
9776     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9777             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9778                 SetHighlights(fromX, fromY, toX, toY);
9779             }
9780     }
9781     DrawPosition(FALSE, boards[currentMove]);
9782     DisplayBothClocks();
9783     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9784 }
9785
9786 void
9787 SendEgtPath (ChessProgramState *cps)
9788 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9789         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9790
9791         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9792
9793         while(*p) {
9794             char c, *q = name+1, *r, *s;
9795
9796             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9797             while(*p && *p != ',') *q++ = *p++;
9798             *q++ = ':'; *q = 0;
9799             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9800                 strcmp(name, ",nalimov:") == 0 ) {
9801                 // take nalimov path from the menu-changeable option first, if it is defined
9802               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9803                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9804             } else
9805             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9806                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9807                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9808                 s = r = StrStr(s, ":") + 1; // beginning of path info
9809                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9810                 c = *r; *r = 0;             // temporarily null-terminate path info
9811                     *--q = 0;               // strip of trailig ':' from name
9812                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9813                 *r = c;
9814                 SendToProgram(buf,cps);     // send egtbpath command for this format
9815             }
9816             if(*p == ',') p++; // read away comma to position for next format name
9817         }
9818 }
9819
9820 void
9821 InitChessProgram (ChessProgramState *cps, int setup)
9822 /* setup needed to setup FRC opening position */
9823 {
9824     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9825     if (appData.noChessProgram) return;
9826     hintRequested = FALSE;
9827     bookRequested = FALSE;
9828
9829     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9830     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9831     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9832     if(cps->memSize) { /* [HGM] memory */
9833       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9834         SendToProgram(buf, cps);
9835     }
9836     SendEgtPath(cps); /* [HGM] EGT */
9837     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9838       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9839         SendToProgram(buf, cps);
9840     }
9841
9842     SendToProgram(cps->initString, cps);
9843     if (gameInfo.variant != VariantNormal &&
9844         gameInfo.variant != VariantLoadable
9845         /* [HGM] also send variant if board size non-standard */
9846         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9847                                             ) {
9848       char *v = VariantName(gameInfo.variant);
9849       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9850         /* [HGM] in protocol 1 we have to assume all variants valid */
9851         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9852         DisplayFatalError(buf, 0, 1);
9853         return;
9854       }
9855
9856       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9857       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9858       if( gameInfo.variant == VariantXiangqi )
9859            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9860       if( gameInfo.variant == VariantShogi )
9861            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9862       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9863            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9864       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9865           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9866            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9867       if( gameInfo.variant == VariantCourier )
9868            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9869       if( gameInfo.variant == VariantSuper )
9870            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9871       if( gameInfo.variant == VariantGreat )
9872            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9873       if( gameInfo.variant == VariantSChess )
9874            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9875       if( gameInfo.variant == VariantGrand )
9876            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9877
9878       if(overruled) {
9879         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9880                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9881            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9882            if(StrStr(cps->variants, b) == NULL) {
9883                // specific sized variant not known, check if general sizing allowed
9884                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9885                    if(StrStr(cps->variants, "boardsize") == NULL) {
9886                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9887                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9888                        DisplayFatalError(buf, 0, 1);
9889                        return;
9890                    }
9891                    /* [HGM] here we really should compare with the maximum supported board size */
9892                }
9893            }
9894       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9895       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9896       SendToProgram(buf, cps);
9897     }
9898     currentlyInitializedVariant = gameInfo.variant;
9899
9900     /* [HGM] send opening position in FRC to first engine */
9901     if(setup) {
9902           SendToProgram("force\n", cps);
9903           SendBoard(cps, 0);
9904           /* engine is now in force mode! Set flag to wake it up after first move. */
9905           setboardSpoiledMachineBlack = 1;
9906     }
9907
9908     if (cps->sendICS) {
9909       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9910       SendToProgram(buf, cps);
9911     }
9912     cps->maybeThinking = FALSE;
9913     cps->offeredDraw = 0;
9914     if (!appData.icsActive) {
9915         SendTimeControl(cps, movesPerSession, timeControl,
9916                         timeIncrement, appData.searchDepth,
9917                         searchTime);
9918     }
9919     if (appData.showThinking
9920         // [HGM] thinking: four options require thinking output to be sent
9921         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9922                                 ) {
9923         SendToProgram("post\n", cps);
9924     }
9925     SendToProgram("hard\n", cps);
9926     if (!appData.ponderNextMove) {
9927         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9928            it without being sure what state we are in first.  "hard"
9929            is not a toggle, so that one is OK.
9930          */
9931         SendToProgram("easy\n", cps);
9932     }
9933     if (cps->usePing) {
9934       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9935       SendToProgram(buf, cps);
9936     }
9937     cps->initDone = TRUE;
9938     ClearEngineOutputPane(cps == &second);
9939 }
9940
9941
9942 void
9943 ResendOptions (ChessProgramState *cps)
9944 { // send the stored value of the options
9945   int i;
9946   char buf[MSG_SIZ];
9947   Option *opt = cps->option;
9948   for(i=0; i<cps->nrOptions; i++, opt++) {
9949       switch(opt->type) {
9950         case Spin:
9951         case Slider:
9952         case CheckBox:
9953             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9954           break;
9955         case ComboBox:
9956           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9957           break;
9958         default:
9959             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9960           break;
9961         case Button:
9962         case SaveButton:
9963           continue;
9964       }
9965       SendToProgram(buf, cps);
9966   }
9967 }
9968
9969 void
9970 StartChessProgram (ChessProgramState *cps)
9971 {
9972     char buf[MSG_SIZ];
9973     int err;
9974
9975     if (appData.noChessProgram) return;
9976     cps->initDone = FALSE;
9977
9978     if (strcmp(cps->host, "localhost") == 0) {
9979         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9980     } else if (*appData.remoteShell == NULLCHAR) {
9981         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9982     } else {
9983         if (*appData.remoteUser == NULLCHAR) {
9984           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9985                     cps->program);
9986         } else {
9987           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9988                     cps->host, appData.remoteUser, cps->program);
9989         }
9990         err = StartChildProcess(buf, "", &cps->pr);
9991     }
9992
9993     if (err != 0) {
9994       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9995         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9996         if(cps != &first) return;
9997         appData.noChessProgram = TRUE;
9998         ThawUI();
9999         SetNCPMode();
10000 //      DisplayFatalError(buf, err, 1);
10001 //      cps->pr = NoProc;
10002 //      cps->isr = NULL;
10003         return;
10004     }
10005
10006     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10007     if (cps->protocolVersion > 1) {
10008       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10009       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10010         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10011         cps->comboCnt = 0;  //                and values of combo boxes
10012       }
10013       SendToProgram(buf, cps);
10014       if(cps->reload) ResendOptions(cps);
10015     } else {
10016       SendToProgram("xboard\n", cps);
10017     }
10018 }
10019
10020 void
10021 TwoMachinesEventIfReady P((void))
10022 {
10023   static int curMess = 0;
10024   if (first.lastPing != first.lastPong) {
10025     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10026     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10027     return;
10028   }
10029   if (second.lastPing != second.lastPong) {
10030     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10031     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10032     return;
10033   }
10034   DisplayMessage("", ""); curMess = 0;
10035   ThawUI();
10036   TwoMachinesEvent();
10037 }
10038
10039 char *
10040 MakeName (char *template)
10041 {
10042     time_t clock;
10043     struct tm *tm;
10044     static char buf[MSG_SIZ];
10045     char *p = buf;
10046     int i;
10047
10048     clock = time((time_t *)NULL);
10049     tm = localtime(&clock);
10050
10051     while(*p++ = *template++) if(p[-1] == '%') {
10052         switch(*template++) {
10053           case 0:   *p = 0; return buf;
10054           case 'Y': i = tm->tm_year+1900; break;
10055           case 'y': i = tm->tm_year-100; break;
10056           case 'M': i = tm->tm_mon+1; break;
10057           case 'd': i = tm->tm_mday; break;
10058           case 'h': i = tm->tm_hour; break;
10059           case 'm': i = tm->tm_min; break;
10060           case 's': i = tm->tm_sec; break;
10061           default:  i = 0;
10062         }
10063         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10064     }
10065     return buf;
10066 }
10067
10068 int
10069 CountPlayers (char *p)
10070 {
10071     int n = 0;
10072     while(p = strchr(p, '\n')) p++, n++; // count participants
10073     return n;
10074 }
10075
10076 FILE *
10077 WriteTourneyFile (char *results, FILE *f)
10078 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10079     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10080     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10081         // create a file with tournament description
10082         fprintf(f, "-participants {%s}\n", appData.participants);
10083         fprintf(f, "-seedBase %d\n", appData.seedBase);
10084         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10085         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10086         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10087         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10088         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10089         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10090         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10091         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10092         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10093         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10094         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10095         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10096         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10097         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10098         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10099         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10100         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10101         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10102         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10103         fprintf(f, "-smpCores %d\n", appData.smpCores);
10104         if(searchTime > 0)
10105                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10106         else {
10107                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10108                 fprintf(f, "-tc %s\n", appData.timeControl);
10109                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10110         }
10111         fprintf(f, "-results \"%s\"\n", results);
10112     }
10113     return f;
10114 }
10115
10116 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10117
10118 void
10119 Substitute (char *participants, int expunge)
10120 {
10121     int i, changed, changes=0, nPlayers=0;
10122     char *p, *q, *r, buf[MSG_SIZ];
10123     if(participants == NULL) return;
10124     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10125     r = p = participants; q = appData.participants;
10126     while(*p && *p == *q) {
10127         if(*p == '\n') r = p+1, nPlayers++;
10128         p++; q++;
10129     }
10130     if(*p) { // difference
10131         while(*p && *p++ != '\n');
10132         while(*q && *q++ != '\n');
10133       changed = nPlayers;
10134         changes = 1 + (strcmp(p, q) != 0);
10135     }
10136     if(changes == 1) { // a single engine mnemonic was changed
10137         q = r; while(*q) nPlayers += (*q++ == '\n');
10138         p = buf; while(*r && (*p = *r++) != '\n') p++;
10139         *p = NULLCHAR;
10140         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10141         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10142         if(mnemonic[i]) { // The substitute is valid
10143             FILE *f;
10144             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10145                 flock(fileno(f), LOCK_EX);
10146                 ParseArgsFromFile(f);
10147                 fseek(f, 0, SEEK_SET);
10148                 FREE(appData.participants); appData.participants = participants;
10149                 if(expunge) { // erase results of replaced engine
10150                     int len = strlen(appData.results), w, b, dummy;
10151                     for(i=0; i<len; i++) {
10152                         Pairing(i, nPlayers, &w, &b, &dummy);
10153                         if((w == changed || b == changed) && appData.results[i] == '*') {
10154                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10155                             fclose(f);
10156                             return;
10157                         }
10158                     }
10159                     for(i=0; i<len; i++) {
10160                         Pairing(i, nPlayers, &w, &b, &dummy);
10161                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10162                     }
10163                 }
10164                 WriteTourneyFile(appData.results, f);
10165                 fclose(f); // release lock
10166                 return;
10167             }
10168         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10169     }
10170     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10171     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10172     free(participants);
10173     return;
10174 }
10175
10176 int
10177 CheckPlayers (char *participants)
10178 {
10179         int i;
10180         char buf[MSG_SIZ], *p;
10181         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10182         while(p = strchr(participants, '\n')) {
10183             *p = NULLCHAR;
10184             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10185             if(!mnemonic[i]) {
10186                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10187                 *p = '\n';
10188                 DisplayError(buf, 0);
10189                 return 1;
10190             }
10191             *p = '\n';
10192             participants = p + 1;
10193         }
10194         return 0;
10195 }
10196
10197 int
10198 CreateTourney (char *name)
10199 {
10200         FILE *f;
10201         if(matchMode && strcmp(name, appData.tourneyFile)) {
10202              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10203         }
10204         if(name[0] == NULLCHAR) {
10205             if(appData.participants[0])
10206                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10207             return 0;
10208         }
10209         f = fopen(name, "r");
10210         if(f) { // file exists
10211             ASSIGN(appData.tourneyFile, name);
10212             ParseArgsFromFile(f); // parse it
10213         } else {
10214             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10215             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10216                 DisplayError(_("Not enough participants"), 0);
10217                 return 0;
10218             }
10219             if(CheckPlayers(appData.participants)) return 0;
10220             ASSIGN(appData.tourneyFile, name);
10221             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10222             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10223         }
10224         fclose(f);
10225         appData.noChessProgram = FALSE;
10226         appData.clockMode = TRUE;
10227         SetGNUMode();
10228         return 1;
10229 }
10230
10231 int
10232 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10233 {
10234     char buf[MSG_SIZ], *p, *q;
10235     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10236     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10237     skip = !all && group[0]; // if group requested, we start in skip mode
10238     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10239         p = names; q = buf; header = 0;
10240         while(*p && *p != '\n') *q++ = *p++;
10241         *q = 0;
10242         if(*p == '\n') p++;
10243         if(buf[0] == '#') {
10244             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10245             depth++; // we must be entering a new group
10246             if(all) continue; // suppress printing group headers when complete list requested
10247             header = 1;
10248             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10249         }
10250         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10251         if(engineList[i]) free(engineList[i]);
10252         engineList[i] = strdup(buf);
10253         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10254         if(engineMnemonic[i]) free(engineMnemonic[i]);
10255         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10256             strcat(buf, " (");
10257             sscanf(q + 8, "%s", buf + strlen(buf));
10258             strcat(buf, ")");
10259         }
10260         engineMnemonic[i] = strdup(buf);
10261         i++;
10262     }
10263     engineList[i] = engineMnemonic[i] = NULL;
10264     return i;
10265 }
10266
10267 // following implemented as macro to avoid type limitations
10268 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10269
10270 void
10271 SwapEngines (int n)
10272 {   // swap settings for first engine and other engine (so far only some selected options)
10273     int h;
10274     char *p;
10275     if(n == 0) return;
10276     SWAP(directory, p)
10277     SWAP(chessProgram, p)
10278     SWAP(isUCI, h)
10279     SWAP(hasOwnBookUCI, h)
10280     SWAP(protocolVersion, h)
10281     SWAP(reuse, h)
10282     SWAP(scoreIsAbsolute, h)
10283     SWAP(timeOdds, h)
10284     SWAP(logo, p)
10285     SWAP(pgnName, p)
10286     SWAP(pvSAN, h)
10287     SWAP(engOptions, p)
10288     SWAP(engInitString, p)
10289     SWAP(computerString, p)
10290     SWAP(features, p)
10291     SWAP(fenOverride, p)
10292     SWAP(NPS, h)
10293     SWAP(accumulateTC, h)
10294     SWAP(host, p)
10295 }
10296
10297 int
10298 GetEngineLine (char *s, int n)
10299 {
10300     int i;
10301     char buf[MSG_SIZ];
10302     extern char *icsNames;
10303     if(!s || !*s) return 0;
10304     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10305     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10306     if(!mnemonic[i]) return 0;
10307     if(n == 11) return 1; // just testing if there was a match
10308     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10309     if(n == 1) SwapEngines(n);
10310     ParseArgsFromString(buf);
10311     if(n == 1) SwapEngines(n);
10312     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10313         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10314         ParseArgsFromString(buf);
10315     }
10316     return 1;
10317 }
10318
10319 int
10320 SetPlayer (int player, char *p)
10321 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10322     int i;
10323     char buf[MSG_SIZ], *engineName;
10324     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10325     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10326     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10327     if(mnemonic[i]) {
10328         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10329         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10330         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10331         ParseArgsFromString(buf);
10332     }
10333     free(engineName);
10334     return i;
10335 }
10336
10337 char *recentEngines;
10338
10339 void
10340 RecentEngineEvent (int nr)
10341 {
10342     int n;
10343 //    SwapEngines(1); // bump first to second
10344 //    ReplaceEngine(&second, 1); // and load it there
10345     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10346     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10347     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10348         ReplaceEngine(&first, 0);
10349         FloatToFront(&appData.recentEngineList, command[n]);
10350     }
10351 }
10352
10353 int
10354 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10355 {   // determine players from game number
10356     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10357
10358     if(appData.tourneyType == 0) {
10359         roundsPerCycle = (nPlayers - 1) | 1;
10360         pairingsPerRound = nPlayers / 2;
10361     } else if(appData.tourneyType > 0) {
10362         roundsPerCycle = nPlayers - appData.tourneyType;
10363         pairingsPerRound = appData.tourneyType;
10364     }
10365     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10366     gamesPerCycle = gamesPerRound * roundsPerCycle;
10367     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10368     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10369     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10370     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10371     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10372     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10373
10374     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10375     if(appData.roundSync) *syncInterval = gamesPerRound;
10376
10377     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10378
10379     if(appData.tourneyType == 0) {
10380         if(curPairing == (nPlayers-1)/2 ) {
10381             *whitePlayer = curRound;
10382             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10383         } else {
10384             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10385             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10386             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10387             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10388         }
10389     } else if(appData.tourneyType > 1) {
10390         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10391         *whitePlayer = curRound + appData.tourneyType;
10392     } else if(appData.tourneyType > 0) {
10393         *whitePlayer = curPairing;
10394         *blackPlayer = curRound + appData.tourneyType;
10395     }
10396
10397     // take care of white/black alternation per round.
10398     // For cycles and games this is already taken care of by default, derived from matchGame!
10399     return curRound & 1;
10400 }
10401
10402 int
10403 NextTourneyGame (int nr, int *swapColors)
10404 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10405     char *p, *q;
10406     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10407     FILE *tf;
10408     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10409     tf = fopen(appData.tourneyFile, "r");
10410     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10411     ParseArgsFromFile(tf); fclose(tf);
10412     InitTimeControls(); // TC might be altered from tourney file
10413
10414     nPlayers = CountPlayers(appData.participants); // count participants
10415     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10416     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10417
10418     if(syncInterval) {
10419         p = q = appData.results;
10420         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10421         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10422             DisplayMessage(_("Waiting for other game(s)"),"");
10423             waitingForGame = TRUE;
10424             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10425             return 0;
10426         }
10427         waitingForGame = FALSE;
10428     }
10429
10430     if(appData.tourneyType < 0) {
10431         if(nr>=0 && !pairingReceived) {
10432             char buf[1<<16];
10433             if(pairing.pr == NoProc) {
10434                 if(!appData.pairingEngine[0]) {
10435                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10436                     return 0;
10437                 }
10438                 StartChessProgram(&pairing); // starts the pairing engine
10439             }
10440             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10441             SendToProgram(buf, &pairing);
10442             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10443             SendToProgram(buf, &pairing);
10444             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10445         }
10446         pairingReceived = 0;                              // ... so we continue here
10447         *swapColors = 0;
10448         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10449         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10450         matchGame = 1; roundNr = nr / syncInterval + 1;
10451     }
10452
10453     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10454
10455     // redefine engines, engine dir, etc.
10456     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10457     if(first.pr == NoProc) {
10458       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10459       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10460     }
10461     if(second.pr == NoProc) {
10462       SwapEngines(1);
10463       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10464       SwapEngines(1);         // and make that valid for second engine by swapping
10465       InitEngine(&second, 1);
10466     }
10467     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10468     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10469     return 1;
10470 }
10471
10472 void
10473 NextMatchGame ()
10474 {   // performs game initialization that does not invoke engines, and then tries to start the game
10475     int res, firstWhite, swapColors = 0;
10476     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10477     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
10478         char buf[MSG_SIZ];
10479         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10480         if(strcmp(buf, currentDebugFile)) { // name has changed
10481             FILE *f = fopen(buf, "w");
10482             if(f) { // if opening the new file failed, just keep using the old one
10483                 ASSIGN(currentDebugFile, buf);
10484                 fclose(debugFP);
10485                 debugFP = f;
10486             }
10487             if(appData.serverFileName) {
10488                 if(serverFP) fclose(serverFP);
10489                 serverFP = fopen(appData.serverFileName, "w");
10490                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10491                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10492             }
10493         }
10494     }
10495     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10496     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10497     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10498     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10499     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10500     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10501     Reset(FALSE, first.pr != NoProc);
10502     res = LoadGameOrPosition(matchGame); // setup game
10503     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10504     if(!res) return; // abort when bad game/pos file
10505     TwoMachinesEvent();
10506 }
10507
10508 void
10509 UserAdjudicationEvent (int result)
10510 {
10511     ChessMove gameResult = GameIsDrawn;
10512
10513     if( result > 0 ) {
10514         gameResult = WhiteWins;
10515     }
10516     else if( result < 0 ) {
10517         gameResult = BlackWins;
10518     }
10519
10520     if( gameMode == TwoMachinesPlay ) {
10521         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10522     }
10523 }
10524
10525
10526 // [HGM] save: calculate checksum of game to make games easily identifiable
10527 int
10528 StringCheckSum (char *s)
10529 {
10530         int i = 0;
10531         if(s==NULL) return 0;
10532         while(*s) i = i*259 + *s++;
10533         return i;
10534 }
10535
10536 int
10537 GameCheckSum ()
10538 {
10539         int i, sum=0;
10540         for(i=backwardMostMove; i<forwardMostMove; i++) {
10541                 sum += pvInfoList[i].depth;
10542                 sum += StringCheckSum(parseList[i]);
10543                 sum += StringCheckSum(commentList[i]);
10544                 sum *= 261;
10545         }
10546         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10547         return sum + StringCheckSum(commentList[i]);
10548 } // end of save patch
10549
10550 void
10551 GameEnds (ChessMove result, char *resultDetails, int whosays)
10552 {
10553     GameMode nextGameMode;
10554     int isIcsGame;
10555     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10556
10557     if(endingGame) return; /* [HGM] crash: forbid recursion */
10558     endingGame = 1;
10559     if(twoBoards) { // [HGM] dual: switch back to one board
10560         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10561         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10562     }
10563     if (appData.debugMode) {
10564       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10565               result, resultDetails ? resultDetails : "(null)", whosays);
10566     }
10567
10568     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10569
10570     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10571
10572     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10573         /* If we are playing on ICS, the server decides when the
10574            game is over, but the engine can offer to draw, claim
10575            a draw, or resign.
10576          */
10577 #if ZIPPY
10578         if (appData.zippyPlay && first.initDone) {
10579             if (result == GameIsDrawn) {
10580                 /* In case draw still needs to be claimed */
10581                 SendToICS(ics_prefix);
10582                 SendToICS("draw\n");
10583             } else if (StrCaseStr(resultDetails, "resign")) {
10584                 SendToICS(ics_prefix);
10585                 SendToICS("resign\n");
10586             }
10587         }
10588 #endif
10589         endingGame = 0; /* [HGM] crash */
10590         return;
10591     }
10592
10593     /* If we're loading the game from a file, stop */
10594     if (whosays == GE_FILE) {
10595       (void) StopLoadGameTimer();
10596       gameFileFP = NULL;
10597     }
10598
10599     /* Cancel draw offers */
10600     first.offeredDraw = second.offeredDraw = 0;
10601
10602     /* If this is an ICS game, only ICS can really say it's done;
10603        if not, anyone can. */
10604     isIcsGame = (gameMode == IcsPlayingWhite ||
10605                  gameMode == IcsPlayingBlack ||
10606                  gameMode == IcsObserving    ||
10607                  gameMode == IcsExamining);
10608
10609     if (!isIcsGame || whosays == GE_ICS) {
10610         /* OK -- not an ICS game, or ICS said it was done */
10611         StopClocks();
10612         if (!isIcsGame && !appData.noChessProgram)
10613           SetUserThinkingEnables();
10614
10615         /* [HGM] if a machine claims the game end we verify this claim */
10616         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10617             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10618                 char claimer;
10619                 ChessMove trueResult = (ChessMove) -1;
10620
10621                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10622                                             first.twoMachinesColor[0] :
10623                                             second.twoMachinesColor[0] ;
10624
10625                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10626                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10627                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10628                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10629                 } else
10630                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10631                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10632                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10633                 } else
10634                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10635                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10636                 }
10637
10638                 // now verify win claims, but not in drop games, as we don't understand those yet
10639                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10640                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10641                     (result == WhiteWins && claimer == 'w' ||
10642                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10643                       if (appData.debugMode) {
10644                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10645                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10646                       }
10647                       if(result != trueResult) {
10648                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10649                               result = claimer == 'w' ? BlackWins : WhiteWins;
10650                               resultDetails = buf;
10651                       }
10652                 } else
10653                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10654                     && (forwardMostMove <= backwardMostMove ||
10655                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10656                         (claimer=='b')==(forwardMostMove&1))
10657                                                                                   ) {
10658                       /* [HGM] verify: draws that were not flagged are false claims */
10659                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10660                       result = claimer == 'w' ? BlackWins : WhiteWins;
10661                       resultDetails = buf;
10662                 }
10663                 /* (Claiming a loss is accepted no questions asked!) */
10664             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10665                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10666                 result = GameUnfinished;
10667                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10668             }
10669             /* [HGM] bare: don't allow bare King to win */
10670             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10671                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10672                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10673                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10674                && result != GameIsDrawn)
10675             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10676                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10677                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10678                         if(p >= 0 && p <= (int)WhiteKing) k++;
10679                 }
10680                 if (appData.debugMode) {
10681                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10682                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10683                 }
10684                 if(k <= 1) {
10685                         result = GameIsDrawn;
10686                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10687                         resultDetails = buf;
10688                 }
10689             }
10690         }
10691
10692
10693         if(serverMoves != NULL && !loadFlag) { char c = '=';
10694             if(result==WhiteWins) c = '+';
10695             if(result==BlackWins) c = '-';
10696             if(resultDetails != NULL)
10697                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10698         }
10699         if (resultDetails != NULL) {
10700             gameInfo.result = result;
10701             gameInfo.resultDetails = StrSave(resultDetails);
10702
10703             /* display last move only if game was not loaded from file */
10704             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10705                 DisplayMove(currentMove - 1);
10706
10707             if (forwardMostMove != 0) {
10708                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10709                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10710                                                                 ) {
10711                     if (*appData.saveGameFile != NULLCHAR) {
10712                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10713                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10714                         else
10715                         SaveGameToFile(appData.saveGameFile, TRUE);
10716                     } else if (appData.autoSaveGames) {
10717                         AutoSaveGame();
10718                     }
10719                     if (*appData.savePositionFile != NULLCHAR) {
10720                         SavePositionToFile(appData.savePositionFile);
10721                     }
10722                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10723                 }
10724             }
10725
10726             /* Tell program how game ended in case it is learning */
10727             /* [HGM] Moved this to after saving the PGN, just in case */
10728             /* engine died and we got here through time loss. In that */
10729             /* case we will get a fatal error writing the pipe, which */
10730             /* would otherwise lose us the PGN.                       */
10731             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10732             /* output during GameEnds should never be fatal anymore   */
10733             if (gameMode == MachinePlaysWhite ||
10734                 gameMode == MachinePlaysBlack ||
10735                 gameMode == TwoMachinesPlay ||
10736                 gameMode == IcsPlayingWhite ||
10737                 gameMode == IcsPlayingBlack ||
10738                 gameMode == BeginningOfGame) {
10739                 char buf[MSG_SIZ];
10740                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10741                         resultDetails);
10742                 if (first.pr != NoProc) {
10743                     SendToProgram(buf, &first);
10744                 }
10745                 if (second.pr != NoProc &&
10746                     gameMode == TwoMachinesPlay) {
10747                     SendToProgram(buf, &second);
10748                 }
10749             }
10750         }
10751
10752         if (appData.icsActive) {
10753             if (appData.quietPlay &&
10754                 (gameMode == IcsPlayingWhite ||
10755                  gameMode == IcsPlayingBlack)) {
10756                 SendToICS(ics_prefix);
10757                 SendToICS("set shout 1\n");
10758             }
10759             nextGameMode = IcsIdle;
10760             ics_user_moved = FALSE;
10761             /* clean up premove.  It's ugly when the game has ended and the
10762              * premove highlights are still on the board.
10763              */
10764             if (gotPremove) {
10765               gotPremove = FALSE;
10766               ClearPremoveHighlights();
10767               DrawPosition(FALSE, boards[currentMove]);
10768             }
10769             if (whosays == GE_ICS) {
10770                 switch (result) {
10771                 case WhiteWins:
10772                     if (gameMode == IcsPlayingWhite)
10773                         PlayIcsWinSound();
10774                     else if(gameMode == IcsPlayingBlack)
10775                         PlayIcsLossSound();
10776                     break;
10777                 case BlackWins:
10778                     if (gameMode == IcsPlayingBlack)
10779                         PlayIcsWinSound();
10780                     else if(gameMode == IcsPlayingWhite)
10781                         PlayIcsLossSound();
10782                     break;
10783                 case GameIsDrawn:
10784                     PlayIcsDrawSound();
10785                     break;
10786                 default:
10787                     PlayIcsUnfinishedSound();
10788                 }
10789             }
10790         } else if (gameMode == EditGame ||
10791                    gameMode == PlayFromGameFile ||
10792                    gameMode == AnalyzeMode ||
10793                    gameMode == AnalyzeFile) {
10794             nextGameMode = gameMode;
10795         } else {
10796             nextGameMode = EndOfGame;
10797         }
10798         pausing = FALSE;
10799         ModeHighlight();
10800     } else {
10801         nextGameMode = gameMode;
10802     }
10803
10804     if (appData.noChessProgram) {
10805         gameMode = nextGameMode;
10806         ModeHighlight();
10807         endingGame = 0; /* [HGM] crash */
10808         return;
10809     }
10810
10811     if (first.reuse) {
10812         /* Put first chess program into idle state */
10813         if (first.pr != NoProc &&
10814             (gameMode == MachinePlaysWhite ||
10815              gameMode == MachinePlaysBlack ||
10816              gameMode == TwoMachinesPlay ||
10817              gameMode == IcsPlayingWhite ||
10818              gameMode == IcsPlayingBlack ||
10819              gameMode == BeginningOfGame)) {
10820             SendToProgram("force\n", &first);
10821             if (first.usePing) {
10822               char buf[MSG_SIZ];
10823               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10824               SendToProgram(buf, &first);
10825             }
10826         }
10827     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10828         /* Kill off first chess program */
10829         if (first.isr != NULL)
10830           RemoveInputSource(first.isr);
10831         first.isr = NULL;
10832
10833         if (first.pr != NoProc) {
10834             ExitAnalyzeMode();
10835             DoSleep( appData.delayBeforeQuit );
10836             SendToProgram("quit\n", &first);
10837             DoSleep( appData.delayAfterQuit );
10838             DestroyChildProcess(first.pr, first.useSigterm);
10839             first.reload = TRUE;
10840         }
10841         first.pr = NoProc;
10842     }
10843     if (second.reuse) {
10844         /* Put second chess program into idle state */
10845         if (second.pr != NoProc &&
10846             gameMode == TwoMachinesPlay) {
10847             SendToProgram("force\n", &second);
10848             if (second.usePing) {
10849               char buf[MSG_SIZ];
10850               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10851               SendToProgram(buf, &second);
10852             }
10853         }
10854     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10855         /* Kill off second chess program */
10856         if (second.isr != NULL)
10857           RemoveInputSource(second.isr);
10858         second.isr = NULL;
10859
10860         if (second.pr != NoProc) {
10861             DoSleep( appData.delayBeforeQuit );
10862             SendToProgram("quit\n", &second);
10863             DoSleep( appData.delayAfterQuit );
10864             DestroyChildProcess(second.pr, second.useSigterm);
10865             second.reload = TRUE;
10866         }
10867         second.pr = NoProc;
10868     }
10869
10870     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10871         char resChar = '=';
10872         switch (result) {
10873         case WhiteWins:
10874           resChar = '+';
10875           if (first.twoMachinesColor[0] == 'w') {
10876             first.matchWins++;
10877           } else {
10878             second.matchWins++;
10879           }
10880           break;
10881         case BlackWins:
10882           resChar = '-';
10883           if (first.twoMachinesColor[0] == 'b') {
10884             first.matchWins++;
10885           } else {
10886             second.matchWins++;
10887           }
10888           break;
10889         case GameUnfinished:
10890           resChar = ' ';
10891         default:
10892           break;
10893         }
10894
10895         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10896         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10897             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10898             ReserveGame(nextGame, resChar); // sets nextGame
10899             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10900             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10901         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10902
10903         if (nextGame <= appData.matchGames && !abortMatch) {
10904             gameMode = nextGameMode;
10905             matchGame = nextGame; // this will be overruled in tourney mode!
10906             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10907             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10908             endingGame = 0; /* [HGM] crash */
10909             return;
10910         } else {
10911             gameMode = nextGameMode;
10912             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10913                      first.tidy, second.tidy,
10914                      first.matchWins, second.matchWins,
10915                      appData.matchGames - (first.matchWins + second.matchWins));
10916             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10917             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10918             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10919             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10920                 first.twoMachinesColor = "black\n";
10921                 second.twoMachinesColor = "white\n";
10922             } else {
10923                 first.twoMachinesColor = "white\n";
10924                 second.twoMachinesColor = "black\n";
10925             }
10926         }
10927     }
10928     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10929         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10930       ExitAnalyzeMode();
10931     gameMode = nextGameMode;
10932     ModeHighlight();
10933     endingGame = 0;  /* [HGM] crash */
10934     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10935         if(matchMode == TRUE) { // match through command line: exit with or without popup
10936             if(ranking) {
10937                 ToNrEvent(forwardMostMove);
10938                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10939                 else ExitEvent(0);
10940             } else DisplayFatalError(buf, 0, 0);
10941         } else { // match through menu; just stop, with or without popup
10942             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10943             ModeHighlight();
10944             if(ranking){
10945                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10946             } else DisplayNote(buf);
10947       }
10948       if(ranking) free(ranking);
10949     }
10950 }
10951
10952 /* Assumes program was just initialized (initString sent).
10953    Leaves program in force mode. */
10954 void
10955 FeedMovesToProgram (ChessProgramState *cps, int upto)
10956 {
10957     int i;
10958
10959     if (appData.debugMode)
10960       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10961               startedFromSetupPosition ? "position and " : "",
10962               backwardMostMove, upto, cps->which);
10963     if(currentlyInitializedVariant != gameInfo.variant) {
10964       char buf[MSG_SIZ];
10965         // [HGM] variantswitch: make engine aware of new variant
10966         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10967                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10968         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10969         SendToProgram(buf, cps);
10970         currentlyInitializedVariant = gameInfo.variant;
10971     }
10972     SendToProgram("force\n", cps);
10973     if (startedFromSetupPosition) {
10974         SendBoard(cps, backwardMostMove);
10975     if (appData.debugMode) {
10976         fprintf(debugFP, "feedMoves\n");
10977     }
10978     }
10979     for (i = backwardMostMove; i < upto; i++) {
10980         SendMoveToProgram(i, cps);
10981     }
10982 }
10983
10984
10985 int
10986 ResurrectChessProgram ()
10987 {
10988      /* The chess program may have exited.
10989         If so, restart it and feed it all the moves made so far. */
10990     static int doInit = 0;
10991
10992     if (appData.noChessProgram) return 1;
10993
10994     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10995         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10996         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10997         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10998     } else {
10999         if (first.pr != NoProc) return 1;
11000         StartChessProgram(&first);
11001     }
11002     InitChessProgram(&first, FALSE);
11003     FeedMovesToProgram(&first, currentMove);
11004
11005     if (!first.sendTime) {
11006         /* can't tell gnuchess what its clock should read,
11007            so we bow to its notion. */
11008         ResetClocks();
11009         timeRemaining[0][currentMove] = whiteTimeRemaining;
11010         timeRemaining[1][currentMove] = blackTimeRemaining;
11011     }
11012
11013     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11014                 appData.icsEngineAnalyze) && first.analysisSupport) {
11015       SendToProgram("analyze\n", &first);
11016       first.analyzing = TRUE;
11017     }
11018     return 1;
11019 }
11020
11021 /*
11022  * Button procedures
11023  */
11024 void
11025 Reset (int redraw, int init)
11026 {
11027     int i;
11028
11029     if (appData.debugMode) {
11030         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11031                 redraw, init, gameMode);
11032     }
11033     CleanupTail(); // [HGM] vari: delete any stored variations
11034     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11035     pausing = pauseExamInvalid = FALSE;
11036     startedFromSetupPosition = blackPlaysFirst = FALSE;
11037     firstMove = TRUE;
11038     whiteFlag = blackFlag = FALSE;
11039     userOfferedDraw = FALSE;
11040     hintRequested = bookRequested = FALSE;
11041     first.maybeThinking = FALSE;
11042     second.maybeThinking = FALSE;
11043     first.bookSuspend = FALSE; // [HGM] book
11044     second.bookSuspend = FALSE;
11045     thinkOutput[0] = NULLCHAR;
11046     lastHint[0] = NULLCHAR;
11047     ClearGameInfo(&gameInfo);
11048     gameInfo.variant = StringToVariant(appData.variant);
11049     ics_user_moved = ics_clock_paused = FALSE;
11050     ics_getting_history = H_FALSE;
11051     ics_gamenum = -1;
11052     white_holding[0] = black_holding[0] = NULLCHAR;
11053     ClearProgramStats();
11054     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11055
11056     ResetFrontEnd();
11057     ClearHighlights();
11058     flipView = appData.flipView;
11059     ClearPremoveHighlights();
11060     gotPremove = FALSE;
11061     alarmSounded = FALSE;
11062
11063     GameEnds(EndOfFile, NULL, GE_PLAYER);
11064     if(appData.serverMovesName != NULL) {
11065         /* [HGM] prepare to make moves file for broadcasting */
11066         clock_t t = clock();
11067         if(serverMoves != NULL) fclose(serverMoves);
11068         serverMoves = fopen(appData.serverMovesName, "r");
11069         if(serverMoves != NULL) {
11070             fclose(serverMoves);
11071             /* delay 15 sec before overwriting, so all clients can see end */
11072             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11073         }
11074         serverMoves = fopen(appData.serverMovesName, "w");
11075     }
11076
11077     ExitAnalyzeMode();
11078     gameMode = BeginningOfGame;
11079     ModeHighlight();
11080     if(appData.icsActive) gameInfo.variant = VariantNormal;
11081     currentMove = forwardMostMove = backwardMostMove = 0;
11082     MarkTargetSquares(1);
11083     InitPosition(redraw);
11084     for (i = 0; i < MAX_MOVES; i++) {
11085         if (commentList[i] != NULL) {
11086             free(commentList[i]);
11087             commentList[i] = NULL;
11088         }
11089     }
11090     ResetClocks();
11091     timeRemaining[0][0] = whiteTimeRemaining;
11092     timeRemaining[1][0] = blackTimeRemaining;
11093
11094     if (first.pr == NoProc) {
11095         StartChessProgram(&first);
11096     }
11097     if (init) {
11098             InitChessProgram(&first, startedFromSetupPosition);
11099     }
11100     DisplayTitle("");
11101     DisplayMessage("", "");
11102     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11103     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11104     ClearMap();        // [HGM] exclude: invalidate map
11105 }
11106
11107 void
11108 AutoPlayGameLoop ()
11109 {
11110     for (;;) {
11111         if (!AutoPlayOneMove())
11112           return;
11113         if (matchMode || appData.timeDelay == 0)
11114           continue;
11115         if (appData.timeDelay < 0)
11116           return;
11117         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11118         break;
11119     }
11120 }
11121
11122 void
11123 AnalyzeNextGame()
11124 {
11125     ReloadGame(1); // next game
11126 }
11127
11128 int
11129 AutoPlayOneMove ()
11130 {
11131     int fromX, fromY, toX, toY;
11132
11133     if (appData.debugMode) {
11134       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11135     }
11136
11137     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11138       return FALSE;
11139
11140     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11141       pvInfoList[currentMove].depth = programStats.depth;
11142       pvInfoList[currentMove].score = programStats.score;
11143       pvInfoList[currentMove].time  = 0;
11144       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11145     }
11146
11147     if (currentMove >= forwardMostMove) {
11148       if(gameMode == AnalyzeFile) {
11149           if(appData.loadGameIndex == -1) {
11150             GameEnds(EndOfFile, NULL, GE_FILE);
11151           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11152           } else {
11153           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11154         }
11155       }
11156 //      gameMode = EndOfGame;
11157 //      ModeHighlight();
11158
11159       /* [AS] Clear current move marker at the end of a game */
11160       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11161
11162       return FALSE;
11163     }
11164
11165     toX = moveList[currentMove][2] - AAA;
11166     toY = moveList[currentMove][3] - ONE;
11167
11168     if (moveList[currentMove][1] == '@') {
11169         if (appData.highlightLastMove) {
11170             SetHighlights(-1, -1, toX, toY);
11171         }
11172     } else {
11173         fromX = moveList[currentMove][0] - AAA;
11174         fromY = moveList[currentMove][1] - ONE;
11175
11176         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11177
11178         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11179
11180         if (appData.highlightLastMove) {
11181             SetHighlights(fromX, fromY, toX, toY);
11182         }
11183     }
11184     DisplayMove(currentMove);
11185     SendMoveToProgram(currentMove++, &first);
11186     DisplayBothClocks();
11187     DrawPosition(FALSE, boards[currentMove]);
11188     // [HGM] PV info: always display, routine tests if empty
11189     DisplayComment(currentMove - 1, commentList[currentMove]);
11190     return TRUE;
11191 }
11192
11193
11194 int
11195 LoadGameOneMove (ChessMove readAhead)
11196 {
11197     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11198     char promoChar = NULLCHAR;
11199     ChessMove moveType;
11200     char move[MSG_SIZ];
11201     char *p, *q;
11202
11203     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11204         gameMode != AnalyzeMode && gameMode != Training) {
11205         gameFileFP = NULL;
11206         return FALSE;
11207     }
11208
11209     yyboardindex = forwardMostMove;
11210     if (readAhead != EndOfFile) {
11211       moveType = readAhead;
11212     } else {
11213       if (gameFileFP == NULL)
11214           return FALSE;
11215       moveType = (ChessMove) Myylex();
11216     }
11217
11218     done = FALSE;
11219     switch (moveType) {
11220       case Comment:
11221         if (appData.debugMode)
11222           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11223         p = yy_text;
11224
11225         /* append the comment but don't display it */
11226         AppendComment(currentMove, p, FALSE);
11227         return TRUE;
11228
11229       case WhiteCapturesEnPassant:
11230       case BlackCapturesEnPassant:
11231       case WhitePromotion:
11232       case BlackPromotion:
11233       case WhiteNonPromotion:
11234       case BlackNonPromotion:
11235       case NormalMove:
11236       case WhiteKingSideCastle:
11237       case WhiteQueenSideCastle:
11238       case BlackKingSideCastle:
11239       case BlackQueenSideCastle:
11240       case WhiteKingSideCastleWild:
11241       case WhiteQueenSideCastleWild:
11242       case BlackKingSideCastleWild:
11243       case BlackQueenSideCastleWild:
11244       /* PUSH Fabien */
11245       case WhiteHSideCastleFR:
11246       case WhiteASideCastleFR:
11247       case BlackHSideCastleFR:
11248       case BlackASideCastleFR:
11249       /* POP Fabien */
11250         if (appData.debugMode)
11251           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11252         fromX = currentMoveString[0] - AAA;
11253         fromY = currentMoveString[1] - ONE;
11254         toX = currentMoveString[2] - AAA;
11255         toY = currentMoveString[3] - ONE;
11256         promoChar = currentMoveString[4];
11257         break;
11258
11259       case WhiteDrop:
11260       case BlackDrop:
11261         if (appData.debugMode)
11262           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11263         fromX = moveType == WhiteDrop ?
11264           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11265         (int) CharToPiece(ToLower(currentMoveString[0]));
11266         fromY = DROP_RANK;
11267         toX = currentMoveString[2] - AAA;
11268         toY = currentMoveString[3] - ONE;
11269         break;
11270
11271       case WhiteWins:
11272       case BlackWins:
11273       case GameIsDrawn:
11274       case GameUnfinished:
11275         if (appData.debugMode)
11276           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11277         p = strchr(yy_text, '{');
11278         if (p == NULL) p = strchr(yy_text, '(');
11279         if (p == NULL) {
11280             p = yy_text;
11281             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11282         } else {
11283             q = strchr(p, *p == '{' ? '}' : ')');
11284             if (q != NULL) *q = NULLCHAR;
11285             p++;
11286         }
11287         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11288         GameEnds(moveType, p, GE_FILE);
11289         done = TRUE;
11290         if (cmailMsgLoaded) {
11291             ClearHighlights();
11292             flipView = WhiteOnMove(currentMove);
11293             if (moveType == GameUnfinished) flipView = !flipView;
11294             if (appData.debugMode)
11295               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11296         }
11297         break;
11298
11299       case EndOfFile:
11300         if (appData.debugMode)
11301           fprintf(debugFP, "Parser hit end of file\n");
11302         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11303           case MT_NONE:
11304           case MT_CHECK:
11305             break;
11306           case MT_CHECKMATE:
11307           case MT_STAINMATE:
11308             if (WhiteOnMove(currentMove)) {
11309                 GameEnds(BlackWins, "Black mates", GE_FILE);
11310             } else {
11311                 GameEnds(WhiteWins, "White mates", GE_FILE);
11312             }
11313             break;
11314           case MT_STALEMATE:
11315             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11316             break;
11317         }
11318         done = TRUE;
11319         break;
11320
11321       case MoveNumberOne:
11322         if (lastLoadGameStart == GNUChessGame) {
11323             /* GNUChessGames have numbers, but they aren't move numbers */
11324             if (appData.debugMode)
11325               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11326                       yy_text, (int) moveType);
11327             return LoadGameOneMove(EndOfFile); /* tail recursion */
11328         }
11329         /* else fall thru */
11330
11331       case XBoardGame:
11332       case GNUChessGame:
11333       case PGNTag:
11334         /* Reached start of next game in file */
11335         if (appData.debugMode)
11336           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11337         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11338           case MT_NONE:
11339           case MT_CHECK:
11340             break;
11341           case MT_CHECKMATE:
11342           case MT_STAINMATE:
11343             if (WhiteOnMove(currentMove)) {
11344                 GameEnds(BlackWins, "Black mates", GE_FILE);
11345             } else {
11346                 GameEnds(WhiteWins, "White mates", GE_FILE);
11347             }
11348             break;
11349           case MT_STALEMATE:
11350             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11351             break;
11352         }
11353         done = TRUE;
11354         break;
11355
11356       case PositionDiagram:     /* should not happen; ignore */
11357       case ElapsedTime:         /* ignore */
11358       case NAG:                 /* ignore */
11359         if (appData.debugMode)
11360           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11361                   yy_text, (int) moveType);
11362         return LoadGameOneMove(EndOfFile); /* tail recursion */
11363
11364       case IllegalMove:
11365         if (appData.testLegality) {
11366             if (appData.debugMode)
11367               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11368             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11369                     (forwardMostMove / 2) + 1,
11370                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11371             DisplayError(move, 0);
11372             done = TRUE;
11373         } else {
11374             if (appData.debugMode)
11375               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11376                       yy_text, currentMoveString);
11377             fromX = currentMoveString[0] - AAA;
11378             fromY = currentMoveString[1] - ONE;
11379             toX = currentMoveString[2] - AAA;
11380             toY = currentMoveString[3] - ONE;
11381             promoChar = currentMoveString[4];
11382         }
11383         break;
11384
11385       case AmbiguousMove:
11386         if (appData.debugMode)
11387           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11388         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11389                 (forwardMostMove / 2) + 1,
11390                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11391         DisplayError(move, 0);
11392         done = TRUE;
11393         break;
11394
11395       default:
11396       case ImpossibleMove:
11397         if (appData.debugMode)
11398           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11399         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11400                 (forwardMostMove / 2) + 1,
11401                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11402         DisplayError(move, 0);
11403         done = TRUE;
11404         break;
11405     }
11406
11407     if (done) {
11408         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11409             DrawPosition(FALSE, boards[currentMove]);
11410             DisplayBothClocks();
11411             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11412               DisplayComment(currentMove - 1, commentList[currentMove]);
11413         }
11414         (void) StopLoadGameTimer();
11415         gameFileFP = NULL;
11416         cmailOldMove = forwardMostMove;
11417         return FALSE;
11418     } else {
11419         /* currentMoveString is set as a side-effect of yylex */
11420
11421         thinkOutput[0] = NULLCHAR;
11422         MakeMove(fromX, fromY, toX, toY, promoChar);
11423         currentMove = forwardMostMove;
11424         return TRUE;
11425     }
11426 }
11427
11428 /* Load the nth game from the given file */
11429 int
11430 LoadGameFromFile (char *filename, int n, char *title, int useList)
11431 {
11432     FILE *f;
11433     char buf[MSG_SIZ];
11434
11435     if (strcmp(filename, "-") == 0) {
11436         f = stdin;
11437         title = "stdin";
11438     } else {
11439         f = fopen(filename, "rb");
11440         if (f == NULL) {
11441           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11442             DisplayError(buf, errno);
11443             return FALSE;
11444         }
11445     }
11446     if (fseek(f, 0, 0) == -1) {
11447         /* f is not seekable; probably a pipe */
11448         useList = FALSE;
11449     }
11450     if (useList && n == 0) {
11451         int error = GameListBuild(f);
11452         if (error) {
11453             DisplayError(_("Cannot build game list"), error);
11454         } else if (!ListEmpty(&gameList) &&
11455                    ((ListGame *) gameList.tailPred)->number > 1) {
11456             GameListPopUp(f, title);
11457             return TRUE;
11458         }
11459         GameListDestroy();
11460         n = 1;
11461     }
11462     if (n == 0) n = 1;
11463     return LoadGame(f, n, title, FALSE);
11464 }
11465
11466
11467 void
11468 MakeRegisteredMove ()
11469 {
11470     int fromX, fromY, toX, toY;
11471     char promoChar;
11472     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11473         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11474           case CMAIL_MOVE:
11475           case CMAIL_DRAW:
11476             if (appData.debugMode)
11477               fprintf(debugFP, "Restoring %s for game %d\n",
11478                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11479
11480             thinkOutput[0] = NULLCHAR;
11481             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11482             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11483             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11484             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11485             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11486             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11487             MakeMove(fromX, fromY, toX, toY, promoChar);
11488             ShowMove(fromX, fromY, toX, toY);
11489
11490             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11491               case MT_NONE:
11492               case MT_CHECK:
11493                 break;
11494
11495               case MT_CHECKMATE:
11496               case MT_STAINMATE:
11497                 if (WhiteOnMove(currentMove)) {
11498                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11499                 } else {
11500                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11501                 }
11502                 break;
11503
11504               case MT_STALEMATE:
11505                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11506                 break;
11507             }
11508
11509             break;
11510
11511           case CMAIL_RESIGN:
11512             if (WhiteOnMove(currentMove)) {
11513                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11514             } else {
11515                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11516             }
11517             break;
11518
11519           case CMAIL_ACCEPT:
11520             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11521             break;
11522
11523           default:
11524             break;
11525         }
11526     }
11527
11528     return;
11529 }
11530
11531 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11532 int
11533 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11534 {
11535     int retVal;
11536
11537     if (gameNumber > nCmailGames) {
11538         DisplayError(_("No more games in this message"), 0);
11539         return FALSE;
11540     }
11541     if (f == lastLoadGameFP) {
11542         int offset = gameNumber - lastLoadGameNumber;
11543         if (offset == 0) {
11544             cmailMsg[0] = NULLCHAR;
11545             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11546                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11547                 nCmailMovesRegistered--;
11548             }
11549             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11550             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11551                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11552             }
11553         } else {
11554             if (! RegisterMove()) return FALSE;
11555         }
11556     }
11557
11558     retVal = LoadGame(f, gameNumber, title, useList);
11559
11560     /* Make move registered during previous look at this game, if any */
11561     MakeRegisteredMove();
11562
11563     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11564         commentList[currentMove]
11565           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11566         DisplayComment(currentMove - 1, commentList[currentMove]);
11567     }
11568
11569     return retVal;
11570 }
11571
11572 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11573 int
11574 ReloadGame (int offset)
11575 {
11576     int gameNumber = lastLoadGameNumber + offset;
11577     if (lastLoadGameFP == NULL) {
11578         DisplayError(_("No game has been loaded yet"), 0);
11579         return FALSE;
11580     }
11581     if (gameNumber <= 0) {
11582         DisplayError(_("Can't back up any further"), 0);
11583         return FALSE;
11584     }
11585     if (cmailMsgLoaded) {
11586         return CmailLoadGame(lastLoadGameFP, gameNumber,
11587                              lastLoadGameTitle, lastLoadGameUseList);
11588     } else {
11589         return LoadGame(lastLoadGameFP, gameNumber,
11590                         lastLoadGameTitle, lastLoadGameUseList);
11591     }
11592 }
11593
11594 int keys[EmptySquare+1];
11595
11596 int
11597 PositionMatches (Board b1, Board b2)
11598 {
11599     int r, f, sum=0;
11600     switch(appData.searchMode) {
11601         case 1: return CompareWithRights(b1, b2);
11602         case 2:
11603             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11604                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11605             }
11606             return TRUE;
11607         case 3:
11608             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11609               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11610                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11611             }
11612             return sum==0;
11613         case 4:
11614             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11615                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11616             }
11617             return sum==0;
11618     }
11619     return TRUE;
11620 }
11621
11622 #define Q_PROMO  4
11623 #define Q_EP     3
11624 #define Q_BCASTL 2
11625 #define Q_WCASTL 1
11626
11627 int pieceList[256], quickBoard[256];
11628 ChessSquare pieceType[256] = { EmptySquare };
11629 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11630 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11631 int soughtTotal, turn;
11632 Boolean epOK, flipSearch;
11633
11634 typedef struct {
11635     unsigned char piece, to;
11636 } Move;
11637
11638 #define DSIZE (250000)
11639
11640 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11641 Move *moveDatabase = initialSpace;
11642 unsigned int movePtr, dataSize = DSIZE;
11643
11644 int
11645 MakePieceList (Board board, int *counts)
11646 {
11647     int r, f, n=Q_PROMO, total=0;
11648     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11649     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11650         int sq = f + (r<<4);
11651         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11652             quickBoard[sq] = ++n;
11653             pieceList[n] = sq;
11654             pieceType[n] = board[r][f];
11655             counts[board[r][f]]++;
11656             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11657             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11658             total++;
11659         }
11660     }
11661     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11662     return total;
11663 }
11664
11665 void
11666 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11667 {
11668     int sq = fromX + (fromY<<4);
11669     int piece = quickBoard[sq];
11670     quickBoard[sq] = 0;
11671     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11672     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11673         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11674         moveDatabase[movePtr++].piece = Q_WCASTL;
11675         quickBoard[sq] = piece;
11676         piece = quickBoard[from]; quickBoard[from] = 0;
11677         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11678     } else
11679     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11680         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11681         moveDatabase[movePtr++].piece = Q_BCASTL;
11682         quickBoard[sq] = piece;
11683         piece = quickBoard[from]; quickBoard[from] = 0;
11684         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11685     } else
11686     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11687         quickBoard[(fromY<<4)+toX] = 0;
11688         moveDatabase[movePtr].piece = Q_EP;
11689         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11690         moveDatabase[movePtr].to = sq;
11691     } else
11692     if(promoPiece != pieceType[piece]) {
11693         moveDatabase[movePtr++].piece = Q_PROMO;
11694         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11695     }
11696     moveDatabase[movePtr].piece = piece;
11697     quickBoard[sq] = piece;
11698     movePtr++;
11699 }
11700
11701 int
11702 PackGame (Board board)
11703 {
11704     Move *newSpace = NULL;
11705     moveDatabase[movePtr].piece = 0; // terminate previous game
11706     if(movePtr > dataSize) {
11707         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11708         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11709         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11710         if(newSpace) {
11711             int i;
11712             Move *p = moveDatabase, *q = newSpace;
11713             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11714             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11715             moveDatabase = newSpace;
11716         } else { // calloc failed, we must be out of memory. Too bad...
11717             dataSize = 0; // prevent calloc events for all subsequent games
11718             return 0;     // and signal this one isn't cached
11719         }
11720     }
11721     movePtr++;
11722     MakePieceList(board, counts);
11723     return movePtr;
11724 }
11725
11726 int
11727 QuickCompare (Board board, int *minCounts, int *maxCounts)
11728 {   // compare according to search mode
11729     int r, f;
11730     switch(appData.searchMode)
11731     {
11732       case 1: // exact position match
11733         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11734         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11735             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11736         }
11737         break;
11738       case 2: // can have extra material on empty squares
11739         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11740             if(board[r][f] == EmptySquare) continue;
11741             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11742         }
11743         break;
11744       case 3: // material with exact Pawn structure
11745         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11746             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11747             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11748         } // fall through to material comparison
11749       case 4: // exact material
11750         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11751         break;
11752       case 6: // material range with given imbalance
11753         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11754         // fall through to range comparison
11755       case 5: // material range
11756         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11757     }
11758     return TRUE;
11759 }
11760
11761 int
11762 QuickScan (Board board, Move *move)
11763 {   // reconstruct game,and compare all positions in it
11764     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11765     do {
11766         int piece = move->piece;
11767         int to = move->to, from = pieceList[piece];
11768         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11769           if(!piece) return -1;
11770           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11771             piece = (++move)->piece;
11772             from = pieceList[piece];
11773             counts[pieceType[piece]]--;
11774             pieceType[piece] = (ChessSquare) move->to;
11775             counts[move->to]++;
11776           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11777             counts[pieceType[quickBoard[to]]]--;
11778             quickBoard[to] = 0; total--;
11779             move++;
11780             continue;
11781           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11782             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11783             from  = pieceList[piece]; // so this must be King
11784             quickBoard[from] = 0;
11785             pieceList[piece] = to;
11786             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11787             quickBoard[from] = 0; // rook
11788             quickBoard[to] = piece;
11789             to = move->to; piece = move->piece;
11790             goto aftercastle;
11791           }
11792         }
11793         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11794         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11795         quickBoard[from] = 0;
11796       aftercastle:
11797         quickBoard[to] = piece;
11798         pieceList[piece] = to;
11799         cnt++; turn ^= 3;
11800         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11801            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11802            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11803                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11804           ) {
11805             static int lastCounts[EmptySquare+1];
11806             int i;
11807             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11808             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11809         } else stretch = 0;
11810         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11811         move++;
11812     } while(1);
11813 }
11814
11815 void
11816 InitSearch ()
11817 {
11818     int r, f;
11819     flipSearch = FALSE;
11820     CopyBoard(soughtBoard, boards[currentMove]);
11821     soughtTotal = MakePieceList(soughtBoard, maxSought);
11822     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11823     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11824     CopyBoard(reverseBoard, boards[currentMove]);
11825     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11826         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11827         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11828         reverseBoard[r][f] = piece;
11829     }
11830     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11831     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11832     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11833                  || (boards[currentMove][CASTLING][2] == NoRights ||
11834                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11835                  && (boards[currentMove][CASTLING][5] == NoRights ||
11836                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11837       ) {
11838         flipSearch = TRUE;
11839         CopyBoard(flipBoard, soughtBoard);
11840         CopyBoard(rotateBoard, reverseBoard);
11841         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11842             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11843             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11844         }
11845     }
11846     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11847     if(appData.searchMode >= 5) {
11848         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11849         MakePieceList(soughtBoard, minSought);
11850         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11851     }
11852     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11853         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11854 }
11855
11856 GameInfo dummyInfo;
11857 static int creatingBook;
11858
11859 int
11860 GameContainsPosition (FILE *f, ListGame *lg)
11861 {
11862     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11863     int fromX, fromY, toX, toY;
11864     char promoChar;
11865     static int initDone=FALSE;
11866
11867     // weed out games based on numerical tag comparison
11868     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11869     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11870     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11871     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11872     if(!initDone) {
11873         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11874         initDone = TRUE;
11875     }
11876     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11877     else CopyBoard(boards[scratch], initialPosition); // default start position
11878     if(lg->moves) {
11879         turn = btm + 1;
11880         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11881         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11882     }
11883     if(btm) plyNr++;
11884     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11885     fseek(f, lg->offset, 0);
11886     yynewfile(f);
11887     while(1) {
11888         yyboardindex = scratch;
11889         quickFlag = plyNr+1;
11890         next = Myylex();
11891         quickFlag = 0;
11892         switch(next) {
11893             case PGNTag:
11894                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11895             default:
11896                 continue;
11897
11898             case XBoardGame:
11899             case GNUChessGame:
11900                 if(plyNr) return -1; // after we have seen moves, this is for new game
11901               continue;
11902
11903             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11904             case ImpossibleMove:
11905             case WhiteWins: // game ends here with these four
11906             case BlackWins:
11907             case GameIsDrawn:
11908             case GameUnfinished:
11909                 return -1;
11910
11911             case IllegalMove:
11912                 if(appData.testLegality) return -1;
11913             case WhiteCapturesEnPassant:
11914             case BlackCapturesEnPassant:
11915             case WhitePromotion:
11916             case BlackPromotion:
11917             case WhiteNonPromotion:
11918             case BlackNonPromotion:
11919             case NormalMove:
11920             case WhiteKingSideCastle:
11921             case WhiteQueenSideCastle:
11922             case BlackKingSideCastle:
11923             case BlackQueenSideCastle:
11924             case WhiteKingSideCastleWild:
11925             case WhiteQueenSideCastleWild:
11926             case BlackKingSideCastleWild:
11927             case BlackQueenSideCastleWild:
11928             case WhiteHSideCastleFR:
11929             case WhiteASideCastleFR:
11930             case BlackHSideCastleFR:
11931             case BlackASideCastleFR:
11932                 fromX = currentMoveString[0] - AAA;
11933                 fromY = currentMoveString[1] - ONE;
11934                 toX = currentMoveString[2] - AAA;
11935                 toY = currentMoveString[3] - ONE;
11936                 promoChar = currentMoveString[4];
11937                 break;
11938             case WhiteDrop:
11939             case BlackDrop:
11940                 fromX = next == WhiteDrop ?
11941                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11942                   (int) CharToPiece(ToLower(currentMoveString[0]));
11943                 fromY = DROP_RANK;
11944                 toX = currentMoveString[2] - AAA;
11945                 toY = currentMoveString[3] - ONE;
11946                 promoChar = 0;
11947                 break;
11948         }
11949         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11950         plyNr++;
11951         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11952         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11953         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11954         if(appData.findMirror) {
11955             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11956             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11957         }
11958     }
11959 }
11960
11961 /* Load the nth game from open file f */
11962 int
11963 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11964 {
11965     ChessMove cm;
11966     char buf[MSG_SIZ];
11967     int gn = gameNumber;
11968     ListGame *lg = NULL;
11969     int numPGNTags = 0;
11970     int err, pos = -1;
11971     GameMode oldGameMode;
11972     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11973
11974     if (appData.debugMode)
11975         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11976
11977     if (gameMode == Training )
11978         SetTrainingModeOff();
11979
11980     oldGameMode = gameMode;
11981     if (gameMode != BeginningOfGame) {
11982       Reset(FALSE, TRUE);
11983     }
11984
11985     gameFileFP = f;
11986     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11987         fclose(lastLoadGameFP);
11988     }
11989
11990     if (useList) {
11991         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11992
11993         if (lg) {
11994             fseek(f, lg->offset, 0);
11995             GameListHighlight(gameNumber);
11996             pos = lg->position;
11997             gn = 1;
11998         }
11999         else {
12000             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12001               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12002             else
12003             DisplayError(_("Game number out of range"), 0);
12004             return FALSE;
12005         }
12006     } else {
12007         GameListDestroy();
12008         if (fseek(f, 0, 0) == -1) {
12009             if (f == lastLoadGameFP ?
12010                 gameNumber == lastLoadGameNumber + 1 :
12011                 gameNumber == 1) {
12012                 gn = 1;
12013             } else {
12014                 DisplayError(_("Can't seek on game file"), 0);
12015                 return FALSE;
12016             }
12017         }
12018     }
12019     lastLoadGameFP = f;
12020     lastLoadGameNumber = gameNumber;
12021     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12022     lastLoadGameUseList = useList;
12023
12024     yynewfile(f);
12025
12026     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12027       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12028                 lg->gameInfo.black);
12029             DisplayTitle(buf);
12030     } else if (*title != NULLCHAR) {
12031         if (gameNumber > 1) {
12032           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12033             DisplayTitle(buf);
12034         } else {
12035             DisplayTitle(title);
12036         }
12037     }
12038
12039     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12040         gameMode = PlayFromGameFile;
12041         ModeHighlight();
12042     }
12043
12044     currentMove = forwardMostMove = backwardMostMove = 0;
12045     CopyBoard(boards[0], initialPosition);
12046     StopClocks();
12047
12048     /*
12049      * Skip the first gn-1 games in the file.
12050      * Also skip over anything that precedes an identifiable
12051      * start of game marker, to avoid being confused by
12052      * garbage at the start of the file.  Currently
12053      * recognized start of game markers are the move number "1",
12054      * the pattern "gnuchess .* game", the pattern
12055      * "^[#;%] [^ ]* game file", and a PGN tag block.
12056      * A game that starts with one of the latter two patterns
12057      * will also have a move number 1, possibly
12058      * following a position diagram.
12059      * 5-4-02: Let's try being more lenient and allowing a game to
12060      * start with an unnumbered move.  Does that break anything?
12061      */
12062     cm = lastLoadGameStart = EndOfFile;
12063     while (gn > 0) {
12064         yyboardindex = forwardMostMove;
12065         cm = (ChessMove) Myylex();
12066         switch (cm) {
12067           case EndOfFile:
12068             if (cmailMsgLoaded) {
12069                 nCmailGames = CMAIL_MAX_GAMES - gn;
12070             } else {
12071                 Reset(TRUE, TRUE);
12072                 DisplayError(_("Game not found in file"), 0);
12073             }
12074             return FALSE;
12075
12076           case GNUChessGame:
12077           case XBoardGame:
12078             gn--;
12079             lastLoadGameStart = cm;
12080             break;
12081
12082           case MoveNumberOne:
12083             switch (lastLoadGameStart) {
12084               case GNUChessGame:
12085               case XBoardGame:
12086               case PGNTag:
12087                 break;
12088               case MoveNumberOne:
12089               case EndOfFile:
12090                 gn--;           /* count this game */
12091                 lastLoadGameStart = cm;
12092                 break;
12093               default:
12094                 /* impossible */
12095                 break;
12096             }
12097             break;
12098
12099           case PGNTag:
12100             switch (lastLoadGameStart) {
12101               case GNUChessGame:
12102               case PGNTag:
12103               case MoveNumberOne:
12104               case EndOfFile:
12105                 gn--;           /* count this game */
12106                 lastLoadGameStart = cm;
12107                 break;
12108               case XBoardGame:
12109                 lastLoadGameStart = cm; /* game counted already */
12110                 break;
12111               default:
12112                 /* impossible */
12113                 break;
12114             }
12115             if (gn > 0) {
12116                 do {
12117                     yyboardindex = forwardMostMove;
12118                     cm = (ChessMove) Myylex();
12119                 } while (cm == PGNTag || cm == Comment);
12120             }
12121             break;
12122
12123           case WhiteWins:
12124           case BlackWins:
12125           case GameIsDrawn:
12126             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12127                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12128                     != CMAIL_OLD_RESULT) {
12129                     nCmailResults ++ ;
12130                     cmailResult[  CMAIL_MAX_GAMES
12131                                 - gn - 1] = CMAIL_OLD_RESULT;
12132                 }
12133             }
12134             break;
12135
12136           case NormalMove:
12137             /* Only a NormalMove can be at the start of a game
12138              * without a position diagram. */
12139             if (lastLoadGameStart == EndOfFile ) {
12140               gn--;
12141               lastLoadGameStart = MoveNumberOne;
12142             }
12143             break;
12144
12145           default:
12146             break;
12147         }
12148     }
12149
12150     if (appData.debugMode)
12151       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12152
12153     if (cm == XBoardGame) {
12154         /* Skip any header junk before position diagram and/or move 1 */
12155         for (;;) {
12156             yyboardindex = forwardMostMove;
12157             cm = (ChessMove) Myylex();
12158
12159             if (cm == EndOfFile ||
12160                 cm == GNUChessGame || cm == XBoardGame) {
12161                 /* Empty game; pretend end-of-file and handle later */
12162                 cm = EndOfFile;
12163                 break;
12164             }
12165
12166             if (cm == MoveNumberOne || cm == PositionDiagram ||
12167                 cm == PGNTag || cm == Comment)
12168               break;
12169         }
12170     } else if (cm == GNUChessGame) {
12171         if (gameInfo.event != NULL) {
12172             free(gameInfo.event);
12173         }
12174         gameInfo.event = StrSave(yy_text);
12175     }
12176
12177     startedFromSetupPosition = FALSE;
12178     while (cm == PGNTag) {
12179         if (appData.debugMode)
12180           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12181         err = ParsePGNTag(yy_text, &gameInfo);
12182         if (!err) numPGNTags++;
12183
12184         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12185         if(gameInfo.variant != oldVariant) {
12186             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12187             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12188             InitPosition(TRUE);
12189             oldVariant = gameInfo.variant;
12190             if (appData.debugMode)
12191               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12192         }
12193
12194
12195         if (gameInfo.fen != NULL) {
12196           Board initial_position;
12197           startedFromSetupPosition = TRUE;
12198           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12199             Reset(TRUE, TRUE);
12200             DisplayError(_("Bad FEN position in file"), 0);
12201             return FALSE;
12202           }
12203           CopyBoard(boards[0], initial_position);
12204           if (blackPlaysFirst) {
12205             currentMove = forwardMostMove = backwardMostMove = 1;
12206             CopyBoard(boards[1], initial_position);
12207             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12208             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12209             timeRemaining[0][1] = whiteTimeRemaining;
12210             timeRemaining[1][1] = blackTimeRemaining;
12211             if (commentList[0] != NULL) {
12212               commentList[1] = commentList[0];
12213               commentList[0] = NULL;
12214             }
12215           } else {
12216             currentMove = forwardMostMove = backwardMostMove = 0;
12217           }
12218           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12219           {   int i;
12220               initialRulePlies = FENrulePlies;
12221               for( i=0; i< nrCastlingRights; i++ )
12222                   initialRights[i] = initial_position[CASTLING][i];
12223           }
12224           yyboardindex = forwardMostMove;
12225           free(gameInfo.fen);
12226           gameInfo.fen = NULL;
12227         }
12228
12229         yyboardindex = forwardMostMove;
12230         cm = (ChessMove) Myylex();
12231
12232         /* Handle comments interspersed among the tags */
12233         while (cm == Comment) {
12234             char *p;
12235             if (appData.debugMode)
12236               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12237             p = yy_text;
12238             AppendComment(currentMove, p, FALSE);
12239             yyboardindex = forwardMostMove;
12240             cm = (ChessMove) Myylex();
12241         }
12242     }
12243
12244     /* don't rely on existence of Event tag since if game was
12245      * pasted from clipboard the Event tag may not exist
12246      */
12247     if (numPGNTags > 0){
12248         char *tags;
12249         if (gameInfo.variant == VariantNormal) {
12250           VariantClass v = StringToVariant(gameInfo.event);
12251           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12252           if(v < VariantShogi) gameInfo.variant = v;
12253         }
12254         if (!matchMode) {
12255           if( appData.autoDisplayTags ) {
12256             tags = PGNTags(&gameInfo);
12257             TagsPopUp(tags, CmailMsg());
12258             free(tags);
12259           }
12260         }
12261     } else {
12262         /* Make something up, but don't display it now */
12263         SetGameInfo();
12264         TagsPopDown();
12265     }
12266
12267     if (cm == PositionDiagram) {
12268         int i, j;
12269         char *p;
12270         Board initial_position;
12271
12272         if (appData.debugMode)
12273           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12274
12275         if (!startedFromSetupPosition) {
12276             p = yy_text;
12277             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12278               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12279                 switch (*p) {
12280                   case '{':
12281                   case '[':
12282                   case '-':
12283                   case ' ':
12284                   case '\t':
12285                   case '\n':
12286                   case '\r':
12287                     break;
12288                   default:
12289                     initial_position[i][j++] = CharToPiece(*p);
12290                     break;
12291                 }
12292             while (*p == ' ' || *p == '\t' ||
12293                    *p == '\n' || *p == '\r') p++;
12294
12295             if (strncmp(p, "black", strlen("black"))==0)
12296               blackPlaysFirst = TRUE;
12297             else
12298               blackPlaysFirst = FALSE;
12299             startedFromSetupPosition = TRUE;
12300
12301             CopyBoard(boards[0], initial_position);
12302             if (blackPlaysFirst) {
12303                 currentMove = forwardMostMove = backwardMostMove = 1;
12304                 CopyBoard(boards[1], initial_position);
12305                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12306                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12307                 timeRemaining[0][1] = whiteTimeRemaining;
12308                 timeRemaining[1][1] = blackTimeRemaining;
12309                 if (commentList[0] != NULL) {
12310                     commentList[1] = commentList[0];
12311                     commentList[0] = NULL;
12312                 }
12313             } else {
12314                 currentMove = forwardMostMove = backwardMostMove = 0;
12315             }
12316         }
12317         yyboardindex = forwardMostMove;
12318         cm = (ChessMove) Myylex();
12319     }
12320
12321   if(!creatingBook) {
12322     if (first.pr == NoProc) {
12323         StartChessProgram(&first);
12324     }
12325     InitChessProgram(&first, FALSE);
12326     SendToProgram("force\n", &first);
12327     if (startedFromSetupPosition) {
12328         SendBoard(&first, forwardMostMove);
12329     if (appData.debugMode) {
12330         fprintf(debugFP, "Load Game\n");
12331     }
12332         DisplayBothClocks();
12333     }
12334   }
12335
12336     /* [HGM] server: flag to write setup moves in broadcast file as one */
12337     loadFlag = appData.suppressLoadMoves;
12338
12339     while (cm == Comment) {
12340         char *p;
12341         if (appData.debugMode)
12342           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12343         p = yy_text;
12344         AppendComment(currentMove, p, FALSE);
12345         yyboardindex = forwardMostMove;
12346         cm = (ChessMove) Myylex();
12347     }
12348
12349     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12350         cm == WhiteWins || cm == BlackWins ||
12351         cm == GameIsDrawn || cm == GameUnfinished) {
12352         DisplayMessage("", _("No moves in game"));
12353         if (cmailMsgLoaded) {
12354             if (appData.debugMode)
12355               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12356             ClearHighlights();
12357             flipView = FALSE;
12358         }
12359         DrawPosition(FALSE, boards[currentMove]);
12360         DisplayBothClocks();
12361         gameMode = EditGame;
12362         ModeHighlight();
12363         gameFileFP = NULL;
12364         cmailOldMove = 0;
12365         return TRUE;
12366     }
12367
12368     // [HGM] PV info: routine tests if comment empty
12369     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12370         DisplayComment(currentMove - 1, commentList[currentMove]);
12371     }
12372     if (!matchMode && appData.timeDelay != 0)
12373       DrawPosition(FALSE, boards[currentMove]);
12374
12375     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12376       programStats.ok_to_send = 1;
12377     }
12378
12379     /* if the first token after the PGN tags is a move
12380      * and not move number 1, retrieve it from the parser
12381      */
12382     if (cm != MoveNumberOne)
12383         LoadGameOneMove(cm);
12384
12385     /* load the remaining moves from the file */
12386     while (LoadGameOneMove(EndOfFile)) {
12387       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12388       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12389     }
12390
12391     /* rewind to the start of the game */
12392     currentMove = backwardMostMove;
12393
12394     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12395
12396     if (oldGameMode == AnalyzeFile ||
12397         oldGameMode == AnalyzeMode) {
12398       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12399       AnalyzeFileEvent();
12400     }
12401
12402     if(creatingBook) return TRUE;
12403     if (!matchMode && pos > 0) {
12404         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12405     } else
12406     if (matchMode || appData.timeDelay == 0) {
12407       ToEndEvent();
12408     } else if (appData.timeDelay > 0) {
12409       AutoPlayGameLoop();
12410     }
12411
12412     if (appData.debugMode)
12413         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12414
12415     loadFlag = 0; /* [HGM] true game starts */
12416     return TRUE;
12417 }
12418
12419 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12420 int
12421 ReloadPosition (int offset)
12422 {
12423     int positionNumber = lastLoadPositionNumber + offset;
12424     if (lastLoadPositionFP == NULL) {
12425         DisplayError(_("No position has been loaded yet"), 0);
12426         return FALSE;
12427     }
12428     if (positionNumber <= 0) {
12429         DisplayError(_("Can't back up any further"), 0);
12430         return FALSE;
12431     }
12432     return LoadPosition(lastLoadPositionFP, positionNumber,
12433                         lastLoadPositionTitle);
12434 }
12435
12436 /* Load the nth position from the given file */
12437 int
12438 LoadPositionFromFile (char *filename, int n, char *title)
12439 {
12440     FILE *f;
12441     char buf[MSG_SIZ];
12442
12443     if (strcmp(filename, "-") == 0) {
12444         return LoadPosition(stdin, n, "stdin");
12445     } else {
12446         f = fopen(filename, "rb");
12447         if (f == NULL) {
12448             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12449             DisplayError(buf, errno);
12450             return FALSE;
12451         } else {
12452             return LoadPosition(f, n, title);
12453         }
12454     }
12455 }
12456
12457 /* Load the nth position from the given open file, and close it */
12458 int
12459 LoadPosition (FILE *f, int positionNumber, char *title)
12460 {
12461     char *p, line[MSG_SIZ];
12462     Board initial_position;
12463     int i, j, fenMode, pn;
12464
12465     if (gameMode == Training )
12466         SetTrainingModeOff();
12467
12468     if (gameMode != BeginningOfGame) {
12469         Reset(FALSE, TRUE);
12470     }
12471     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12472         fclose(lastLoadPositionFP);
12473     }
12474     if (positionNumber == 0) positionNumber = 1;
12475     lastLoadPositionFP = f;
12476     lastLoadPositionNumber = positionNumber;
12477     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12478     if (first.pr == NoProc && !appData.noChessProgram) {
12479       StartChessProgram(&first);
12480       InitChessProgram(&first, FALSE);
12481     }
12482     pn = positionNumber;
12483     if (positionNumber < 0) {
12484         /* Negative position number means to seek to that byte offset */
12485         if (fseek(f, -positionNumber, 0) == -1) {
12486             DisplayError(_("Can't seek on position file"), 0);
12487             return FALSE;
12488         };
12489         pn = 1;
12490     } else {
12491         if (fseek(f, 0, 0) == -1) {
12492             if (f == lastLoadPositionFP ?
12493                 positionNumber == lastLoadPositionNumber + 1 :
12494                 positionNumber == 1) {
12495                 pn = 1;
12496             } else {
12497                 DisplayError(_("Can't seek on position file"), 0);
12498                 return FALSE;
12499             }
12500         }
12501     }
12502     /* See if this file is FEN or old-style xboard */
12503     if (fgets(line, MSG_SIZ, f) == NULL) {
12504         DisplayError(_("Position not found in file"), 0);
12505         return FALSE;
12506     }
12507     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12508     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12509
12510     if (pn >= 2) {
12511         if (fenMode || line[0] == '#') pn--;
12512         while (pn > 0) {
12513             /* skip positions before number pn */
12514             if (fgets(line, MSG_SIZ, f) == NULL) {
12515                 Reset(TRUE, TRUE);
12516                 DisplayError(_("Position not found in file"), 0);
12517                 return FALSE;
12518             }
12519             if (fenMode || line[0] == '#') pn--;
12520         }
12521     }
12522
12523     if (fenMode) {
12524         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12525             DisplayError(_("Bad FEN position in file"), 0);
12526             return FALSE;
12527         }
12528     } else {
12529         (void) fgets(line, MSG_SIZ, f);
12530         (void) fgets(line, MSG_SIZ, f);
12531
12532         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12533             (void) fgets(line, MSG_SIZ, f);
12534             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12535                 if (*p == ' ')
12536                   continue;
12537                 initial_position[i][j++] = CharToPiece(*p);
12538             }
12539         }
12540
12541         blackPlaysFirst = FALSE;
12542         if (!feof(f)) {
12543             (void) fgets(line, MSG_SIZ, f);
12544             if (strncmp(line, "black", strlen("black"))==0)
12545               blackPlaysFirst = TRUE;
12546         }
12547     }
12548     startedFromSetupPosition = TRUE;
12549
12550     CopyBoard(boards[0], initial_position);
12551     if (blackPlaysFirst) {
12552         currentMove = forwardMostMove = backwardMostMove = 1;
12553         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12554         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12555         CopyBoard(boards[1], initial_position);
12556         DisplayMessage("", _("Black to play"));
12557     } else {
12558         currentMove = forwardMostMove = backwardMostMove = 0;
12559         DisplayMessage("", _("White to play"));
12560     }
12561     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12562     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12563         SendToProgram("force\n", &first);
12564         SendBoard(&first, forwardMostMove);
12565     }
12566     if (appData.debugMode) {
12567 int i, j;
12568   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12569   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12570         fprintf(debugFP, "Load Position\n");
12571     }
12572
12573     if (positionNumber > 1) {
12574       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12575         DisplayTitle(line);
12576     } else {
12577         DisplayTitle(title);
12578     }
12579     gameMode = EditGame;
12580     ModeHighlight();
12581     ResetClocks();
12582     timeRemaining[0][1] = whiteTimeRemaining;
12583     timeRemaining[1][1] = blackTimeRemaining;
12584     DrawPosition(FALSE, boards[currentMove]);
12585
12586     return TRUE;
12587 }
12588
12589
12590 void
12591 CopyPlayerNameIntoFileName (char **dest, char *src)
12592 {
12593     while (*src != NULLCHAR && *src != ',') {
12594         if (*src == ' ') {
12595             *(*dest)++ = '_';
12596             src++;
12597         } else {
12598             *(*dest)++ = *src++;
12599         }
12600     }
12601 }
12602
12603 char *
12604 DefaultFileName (char *ext)
12605 {
12606     static char def[MSG_SIZ];
12607     char *p;
12608
12609     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12610         p = def;
12611         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12612         *p++ = '-';
12613         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12614         *p++ = '.';
12615         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12616     } else {
12617         def[0] = NULLCHAR;
12618     }
12619     return def;
12620 }
12621
12622 /* Save the current game to the given file */
12623 int
12624 SaveGameToFile (char *filename, int append)
12625 {
12626     FILE *f;
12627     char buf[MSG_SIZ];
12628     int result, i, t,tot=0;
12629
12630     if (strcmp(filename, "-") == 0) {
12631         return SaveGame(stdout, 0, NULL);
12632     } else {
12633         for(i=0; i<10; i++) { // upto 10 tries
12634              f = fopen(filename, append ? "a" : "w");
12635              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12636              if(f || errno != 13) break;
12637              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12638              tot += t;
12639         }
12640         if (f == NULL) {
12641             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12642             DisplayError(buf, errno);
12643             return FALSE;
12644         } else {
12645             safeStrCpy(buf, lastMsg, MSG_SIZ);
12646             DisplayMessage(_("Waiting for access to save file"), "");
12647             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12648             DisplayMessage(_("Saving game"), "");
12649             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12650             result = SaveGame(f, 0, NULL);
12651             DisplayMessage(buf, "");
12652             return result;
12653         }
12654     }
12655 }
12656
12657 char *
12658 SavePart (char *str)
12659 {
12660     static char buf[MSG_SIZ];
12661     char *p;
12662
12663     p = strchr(str, ' ');
12664     if (p == NULL) return str;
12665     strncpy(buf, str, p - str);
12666     buf[p - str] = NULLCHAR;
12667     return buf;
12668 }
12669
12670 #define PGN_MAX_LINE 75
12671
12672 #define PGN_SIDE_WHITE  0
12673 #define PGN_SIDE_BLACK  1
12674
12675 static int
12676 FindFirstMoveOutOfBook (int side)
12677 {
12678     int result = -1;
12679
12680     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12681         int index = backwardMostMove;
12682         int has_book_hit = 0;
12683
12684         if( (index % 2) != side ) {
12685             index++;
12686         }
12687
12688         while( index < forwardMostMove ) {
12689             /* Check to see if engine is in book */
12690             int depth = pvInfoList[index].depth;
12691             int score = pvInfoList[index].score;
12692             int in_book = 0;
12693
12694             if( depth <= 2 ) {
12695                 in_book = 1;
12696             }
12697             else if( score == 0 && depth == 63 ) {
12698                 in_book = 1; /* Zappa */
12699             }
12700             else if( score == 2 && depth == 99 ) {
12701                 in_book = 1; /* Abrok */
12702             }
12703
12704             has_book_hit += in_book;
12705
12706             if( ! in_book ) {
12707                 result = index;
12708
12709                 break;
12710             }
12711
12712             index += 2;
12713         }
12714     }
12715
12716     return result;
12717 }
12718
12719 void
12720 GetOutOfBookInfo (char * buf)
12721 {
12722     int oob[2];
12723     int i;
12724     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12725
12726     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12727     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12728
12729     *buf = '\0';
12730
12731     if( oob[0] >= 0 || oob[1] >= 0 ) {
12732         for( i=0; i<2; i++ ) {
12733             int idx = oob[i];
12734
12735             if( idx >= 0 ) {
12736                 if( i > 0 && oob[0] >= 0 ) {
12737                     strcat( buf, "   " );
12738                 }
12739
12740                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12741                 sprintf( buf+strlen(buf), "%s%.2f",
12742                     pvInfoList[idx].score >= 0 ? "+" : "",
12743                     pvInfoList[idx].score / 100.0 );
12744             }
12745         }
12746     }
12747 }
12748
12749 /* Save game in PGN style and close the file */
12750 int
12751 SaveGamePGN (FILE *f)
12752 {
12753     int i, offset, linelen, newblock;
12754 //    char *movetext;
12755     char numtext[32];
12756     int movelen, numlen, blank;
12757     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12758
12759     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12760
12761     PrintPGNTags(f, &gameInfo);
12762
12763     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12764
12765     if (backwardMostMove > 0 || startedFromSetupPosition) {
12766         char *fen = PositionToFEN(backwardMostMove, NULL);
12767         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12768         fprintf(f, "\n{--------------\n");
12769         PrintPosition(f, backwardMostMove);
12770         fprintf(f, "--------------}\n");
12771         free(fen);
12772     }
12773     else {
12774         /* [AS] Out of book annotation */
12775         if( appData.saveOutOfBookInfo ) {
12776             char buf[64];
12777
12778             GetOutOfBookInfo( buf );
12779
12780             if( buf[0] != '\0' ) {
12781                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12782             }
12783         }
12784
12785         fprintf(f, "\n");
12786     }
12787
12788     i = backwardMostMove;
12789     linelen = 0;
12790     newblock = TRUE;
12791
12792     while (i < forwardMostMove) {
12793         /* Print comments preceding this move */
12794         if (commentList[i] != NULL) {
12795             if (linelen > 0) fprintf(f, "\n");
12796             fprintf(f, "%s", commentList[i]);
12797             linelen = 0;
12798             newblock = TRUE;
12799         }
12800
12801         /* Format move number */
12802         if ((i % 2) == 0)
12803           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12804         else
12805           if (newblock)
12806             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12807           else
12808             numtext[0] = NULLCHAR;
12809
12810         numlen = strlen(numtext);
12811         newblock = FALSE;
12812
12813         /* Print move number */
12814         blank = linelen > 0 && numlen > 0;
12815         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12816             fprintf(f, "\n");
12817             linelen = 0;
12818             blank = 0;
12819         }
12820         if (blank) {
12821             fprintf(f, " ");
12822             linelen++;
12823         }
12824         fprintf(f, "%s", numtext);
12825         linelen += numlen;
12826
12827         /* Get move */
12828         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12829         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12830
12831         /* Print move */
12832         blank = linelen > 0 && movelen > 0;
12833         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12834             fprintf(f, "\n");
12835             linelen = 0;
12836             blank = 0;
12837         }
12838         if (blank) {
12839             fprintf(f, " ");
12840             linelen++;
12841         }
12842         fprintf(f, "%s", move_buffer);
12843         linelen += movelen;
12844
12845         /* [AS] Add PV info if present */
12846         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12847             /* [HGM] add time */
12848             char buf[MSG_SIZ]; int seconds;
12849
12850             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12851
12852             if( seconds <= 0)
12853               buf[0] = 0;
12854             else
12855               if( seconds < 30 )
12856                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12857               else
12858                 {
12859                   seconds = (seconds + 4)/10; // round to full seconds
12860                   if( seconds < 60 )
12861                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12862                   else
12863                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12864                 }
12865
12866             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12867                       pvInfoList[i].score >= 0 ? "+" : "",
12868                       pvInfoList[i].score / 100.0,
12869                       pvInfoList[i].depth,
12870                       buf );
12871
12872             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12873
12874             /* Print score/depth */
12875             blank = linelen > 0 && movelen > 0;
12876             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12877                 fprintf(f, "\n");
12878                 linelen = 0;
12879                 blank = 0;
12880             }
12881             if (blank) {
12882                 fprintf(f, " ");
12883                 linelen++;
12884             }
12885             fprintf(f, "%s", move_buffer);
12886             linelen += movelen;
12887         }
12888
12889         i++;
12890     }
12891
12892     /* Start a new line */
12893     if (linelen > 0) fprintf(f, "\n");
12894
12895     /* Print comments after last move */
12896     if (commentList[i] != NULL) {
12897         fprintf(f, "%s\n", commentList[i]);
12898     }
12899
12900     /* Print result */
12901     if (gameInfo.resultDetails != NULL &&
12902         gameInfo.resultDetails[0] != NULLCHAR) {
12903         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12904                 PGNResult(gameInfo.result));
12905     } else {
12906         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12907     }
12908
12909     fclose(f);
12910     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12911     return TRUE;
12912 }
12913
12914 /* Save game in old style and close the file */
12915 int
12916 SaveGameOldStyle (FILE *f)
12917 {
12918     int i, offset;
12919     time_t tm;
12920
12921     tm = time((time_t *) NULL);
12922
12923     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12924     PrintOpponents(f);
12925
12926     if (backwardMostMove > 0 || startedFromSetupPosition) {
12927         fprintf(f, "\n[--------------\n");
12928         PrintPosition(f, backwardMostMove);
12929         fprintf(f, "--------------]\n");
12930     } else {
12931         fprintf(f, "\n");
12932     }
12933
12934     i = backwardMostMove;
12935     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12936
12937     while (i < forwardMostMove) {
12938         if (commentList[i] != NULL) {
12939             fprintf(f, "[%s]\n", commentList[i]);
12940         }
12941
12942         if ((i % 2) == 1) {
12943             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12944             i++;
12945         } else {
12946             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12947             i++;
12948             if (commentList[i] != NULL) {
12949                 fprintf(f, "\n");
12950                 continue;
12951             }
12952             if (i >= forwardMostMove) {
12953                 fprintf(f, "\n");
12954                 break;
12955             }
12956             fprintf(f, "%s\n", parseList[i]);
12957             i++;
12958         }
12959     }
12960
12961     if (commentList[i] != NULL) {
12962         fprintf(f, "[%s]\n", commentList[i]);
12963     }
12964
12965     /* This isn't really the old style, but it's close enough */
12966     if (gameInfo.resultDetails != NULL &&
12967         gameInfo.resultDetails[0] != NULLCHAR) {
12968         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12969                 gameInfo.resultDetails);
12970     } else {
12971         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12972     }
12973
12974     fclose(f);
12975     return TRUE;
12976 }
12977
12978 /* Save the current game to open file f and close the file */
12979 int
12980 SaveGame (FILE *f, int dummy, char *dummy2)
12981 {
12982     if (gameMode == EditPosition) EditPositionDone(TRUE);
12983     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12984     if (appData.oldSaveStyle)
12985       return SaveGameOldStyle(f);
12986     else
12987       return SaveGamePGN(f);
12988 }
12989
12990 /* Save the current position to the given file */
12991 int
12992 SavePositionToFile (char *filename)
12993 {
12994     FILE *f;
12995     char buf[MSG_SIZ];
12996
12997     if (strcmp(filename, "-") == 0) {
12998         return SavePosition(stdout, 0, NULL);
12999     } else {
13000         f = fopen(filename, "a");
13001         if (f == NULL) {
13002             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13003             DisplayError(buf, errno);
13004             return FALSE;
13005         } else {
13006             safeStrCpy(buf, lastMsg, MSG_SIZ);
13007             DisplayMessage(_("Waiting for access to save file"), "");
13008             flock(fileno(f), LOCK_EX); // [HGM] lock
13009             DisplayMessage(_("Saving position"), "");
13010             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13011             SavePosition(f, 0, NULL);
13012             DisplayMessage(buf, "");
13013             return TRUE;
13014         }
13015     }
13016 }
13017
13018 /* Save the current position to the given open file and close the file */
13019 int
13020 SavePosition (FILE *f, int dummy, char *dummy2)
13021 {
13022     time_t tm;
13023     char *fen;
13024
13025     if (gameMode == EditPosition) EditPositionDone(TRUE);
13026     if (appData.oldSaveStyle) {
13027         tm = time((time_t *) NULL);
13028
13029         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13030         PrintOpponents(f);
13031         fprintf(f, "[--------------\n");
13032         PrintPosition(f, currentMove);
13033         fprintf(f, "--------------]\n");
13034     } else {
13035         fen = PositionToFEN(currentMove, NULL);
13036         fprintf(f, "%s\n", fen);
13037         free(fen);
13038     }
13039     fclose(f);
13040     return TRUE;
13041 }
13042
13043 void
13044 ReloadCmailMsgEvent (int unregister)
13045 {
13046 #if !WIN32
13047     static char *inFilename = NULL;
13048     static char *outFilename;
13049     int i;
13050     struct stat inbuf, outbuf;
13051     int status;
13052
13053     /* Any registered moves are unregistered if unregister is set, */
13054     /* i.e. invoked by the signal handler */
13055     if (unregister) {
13056         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13057             cmailMoveRegistered[i] = FALSE;
13058             if (cmailCommentList[i] != NULL) {
13059                 free(cmailCommentList[i]);
13060                 cmailCommentList[i] = NULL;
13061             }
13062         }
13063         nCmailMovesRegistered = 0;
13064     }
13065
13066     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13067         cmailResult[i] = CMAIL_NOT_RESULT;
13068     }
13069     nCmailResults = 0;
13070
13071     if (inFilename == NULL) {
13072         /* Because the filenames are static they only get malloced once  */
13073         /* and they never get freed                                      */
13074         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13075         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13076
13077         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13078         sprintf(outFilename, "%s.out", appData.cmailGameName);
13079     }
13080
13081     status = stat(outFilename, &outbuf);
13082     if (status < 0) {
13083         cmailMailedMove = FALSE;
13084     } else {
13085         status = stat(inFilename, &inbuf);
13086         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13087     }
13088
13089     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13090        counts the games, notes how each one terminated, etc.
13091
13092        It would be nice to remove this kludge and instead gather all
13093        the information while building the game list.  (And to keep it
13094        in the game list nodes instead of having a bunch of fixed-size
13095        parallel arrays.)  Note this will require getting each game's
13096        termination from the PGN tags, as the game list builder does
13097        not process the game moves.  --mann
13098        */
13099     cmailMsgLoaded = TRUE;
13100     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13101
13102     /* Load first game in the file or popup game menu */
13103     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13104
13105 #endif /* !WIN32 */
13106     return;
13107 }
13108
13109 int
13110 RegisterMove ()
13111 {
13112     FILE *f;
13113     char string[MSG_SIZ];
13114
13115     if (   cmailMailedMove
13116         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13117         return TRUE;            /* Allow free viewing  */
13118     }
13119
13120     /* Unregister move to ensure that we don't leave RegisterMove        */
13121     /* with the move registered when the conditions for registering no   */
13122     /* longer hold                                                       */
13123     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13124         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13125         nCmailMovesRegistered --;
13126
13127         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13128           {
13129               free(cmailCommentList[lastLoadGameNumber - 1]);
13130               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13131           }
13132     }
13133
13134     if (cmailOldMove == -1) {
13135         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13136         return FALSE;
13137     }
13138
13139     if (currentMove > cmailOldMove + 1) {
13140         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13141         return FALSE;
13142     }
13143
13144     if (currentMove < cmailOldMove) {
13145         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13146         return FALSE;
13147     }
13148
13149     if (forwardMostMove > currentMove) {
13150         /* Silently truncate extra moves */
13151         TruncateGame();
13152     }
13153
13154     if (   (currentMove == cmailOldMove + 1)
13155         || (   (currentMove == cmailOldMove)
13156             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13157                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13158         if (gameInfo.result != GameUnfinished) {
13159             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13160         }
13161
13162         if (commentList[currentMove] != NULL) {
13163             cmailCommentList[lastLoadGameNumber - 1]
13164               = StrSave(commentList[currentMove]);
13165         }
13166         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13167
13168         if (appData.debugMode)
13169           fprintf(debugFP, "Saving %s for game %d\n",
13170                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13171
13172         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13173
13174         f = fopen(string, "w");
13175         if (appData.oldSaveStyle) {
13176             SaveGameOldStyle(f); /* also closes the file */
13177
13178             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13179             f = fopen(string, "w");
13180             SavePosition(f, 0, NULL); /* also closes the file */
13181         } else {
13182             fprintf(f, "{--------------\n");
13183             PrintPosition(f, currentMove);
13184             fprintf(f, "--------------}\n\n");
13185
13186             SaveGame(f, 0, NULL); /* also closes the file*/
13187         }
13188
13189         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13190         nCmailMovesRegistered ++;
13191     } else if (nCmailGames == 1) {
13192         DisplayError(_("You have not made a move yet"), 0);
13193         return FALSE;
13194     }
13195
13196     return TRUE;
13197 }
13198
13199 void
13200 MailMoveEvent ()
13201 {
13202 #if !WIN32
13203     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13204     FILE *commandOutput;
13205     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13206     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13207     int nBuffers;
13208     int i;
13209     int archived;
13210     char *arcDir;
13211
13212     if (! cmailMsgLoaded) {
13213         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13214         return;
13215     }
13216
13217     if (nCmailGames == nCmailResults) {
13218         DisplayError(_("No unfinished games"), 0);
13219         return;
13220     }
13221
13222 #if CMAIL_PROHIBIT_REMAIL
13223     if (cmailMailedMove) {
13224       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);
13225         DisplayError(msg, 0);
13226         return;
13227     }
13228 #endif
13229
13230     if (! (cmailMailedMove || RegisterMove())) return;
13231
13232     if (   cmailMailedMove
13233         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13234       snprintf(string, MSG_SIZ, partCommandString,
13235                appData.debugMode ? " -v" : "", appData.cmailGameName);
13236         commandOutput = popen(string, "r");
13237
13238         if (commandOutput == NULL) {
13239             DisplayError(_("Failed to invoke cmail"), 0);
13240         } else {
13241             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13242                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13243             }
13244             if (nBuffers > 1) {
13245                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13246                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13247                 nBytes = MSG_SIZ - 1;
13248             } else {
13249                 (void) memcpy(msg, buffer, nBytes);
13250             }
13251             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13252
13253             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13254                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13255
13256                 archived = TRUE;
13257                 for (i = 0; i < nCmailGames; i ++) {
13258                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13259                         archived = FALSE;
13260                     }
13261                 }
13262                 if (   archived
13263                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13264                         != NULL)) {
13265                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13266                            arcDir,
13267                            appData.cmailGameName,
13268                            gameInfo.date);
13269                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13270                     cmailMsgLoaded = FALSE;
13271                 }
13272             }
13273
13274             DisplayInformation(msg);
13275             pclose(commandOutput);
13276         }
13277     } else {
13278         if ((*cmailMsg) != '\0') {
13279             DisplayInformation(cmailMsg);
13280         }
13281     }
13282
13283     return;
13284 #endif /* !WIN32 */
13285 }
13286
13287 char *
13288 CmailMsg ()
13289 {
13290 #if WIN32
13291     return NULL;
13292 #else
13293     int  prependComma = 0;
13294     char number[5];
13295     char string[MSG_SIZ];       /* Space for game-list */
13296     int  i;
13297
13298     if (!cmailMsgLoaded) return "";
13299
13300     if (cmailMailedMove) {
13301       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13302     } else {
13303         /* Create a list of games left */
13304       snprintf(string, MSG_SIZ, "[");
13305         for (i = 0; i < nCmailGames; i ++) {
13306             if (! (   cmailMoveRegistered[i]
13307                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13308                 if (prependComma) {
13309                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13310                 } else {
13311                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13312                     prependComma = 1;
13313                 }
13314
13315                 strcat(string, number);
13316             }
13317         }
13318         strcat(string, "]");
13319
13320         if (nCmailMovesRegistered + nCmailResults == 0) {
13321             switch (nCmailGames) {
13322               case 1:
13323                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13324                 break;
13325
13326               case 2:
13327                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13328                 break;
13329
13330               default:
13331                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13332                          nCmailGames);
13333                 break;
13334             }
13335         } else {
13336             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13337               case 1:
13338                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13339                          string);
13340                 break;
13341
13342               case 0:
13343                 if (nCmailResults == nCmailGames) {
13344                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13345                 } else {
13346                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13347                 }
13348                 break;
13349
13350               default:
13351                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13352                          string);
13353             }
13354         }
13355     }
13356     return cmailMsg;
13357 #endif /* WIN32 */
13358 }
13359
13360 void
13361 ResetGameEvent ()
13362 {
13363     if (gameMode == Training)
13364       SetTrainingModeOff();
13365
13366     Reset(TRUE, TRUE);
13367     cmailMsgLoaded = FALSE;
13368     if (appData.icsActive) {
13369       SendToICS(ics_prefix);
13370       SendToICS("refresh\n");
13371     }
13372 }
13373
13374 void
13375 ExitEvent (int status)
13376 {
13377     exiting++;
13378     if (exiting > 2) {
13379       /* Give up on clean exit */
13380       exit(status);
13381     }
13382     if (exiting > 1) {
13383       /* Keep trying for clean exit */
13384       return;
13385     }
13386
13387     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13388
13389     if (telnetISR != NULL) {
13390       RemoveInputSource(telnetISR);
13391     }
13392     if (icsPR != NoProc) {
13393       DestroyChildProcess(icsPR, TRUE);
13394     }
13395
13396     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13397     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13398
13399     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13400     /* make sure this other one finishes before killing it!                  */
13401     if(endingGame) { int count = 0;
13402         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13403         while(endingGame && count++ < 10) DoSleep(1);
13404         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13405     }
13406
13407     /* Kill off chess programs */
13408     if (first.pr != NoProc) {
13409         ExitAnalyzeMode();
13410
13411         DoSleep( appData.delayBeforeQuit );
13412         SendToProgram("quit\n", &first);
13413         DoSleep( appData.delayAfterQuit );
13414         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13415     }
13416     if (second.pr != NoProc) {
13417         DoSleep( appData.delayBeforeQuit );
13418         SendToProgram("quit\n", &second);
13419         DoSleep( appData.delayAfterQuit );
13420         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13421     }
13422     if (first.isr != NULL) {
13423         RemoveInputSource(first.isr);
13424     }
13425     if (second.isr != NULL) {
13426         RemoveInputSource(second.isr);
13427     }
13428
13429     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13430     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13431
13432     ShutDownFrontEnd();
13433     exit(status);
13434 }
13435
13436 void
13437 PauseEngine (ChessProgramState *cps)
13438 {
13439     SendToProgram("pause\n", cps);
13440     cps->pause = 2;
13441 }
13442
13443 void
13444 UnPauseEngine (ChessProgramState *cps)
13445 {
13446     SendToProgram("resume\n", cps);
13447     cps->pause = 1;
13448 }
13449
13450 void
13451 PauseEvent ()
13452 {
13453     if (appData.debugMode)
13454         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13455     if (pausing) {
13456         pausing = FALSE;
13457         ModeHighlight();
13458         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13459             StartClocks();
13460             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13461                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13462                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13463             }
13464             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13465             HandleMachineMove(stashedInputMove, stalledEngine);
13466             stalledEngine = NULL;
13467             return;
13468         }
13469         if (gameMode == MachinePlaysWhite ||
13470             gameMode == TwoMachinesPlay   ||
13471             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13472             if(first.pause)  UnPauseEngine(&first);
13473             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13474             if(second.pause) UnPauseEngine(&second);
13475             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13476             StartClocks();
13477         } else {
13478             DisplayBothClocks();
13479         }
13480         if (gameMode == PlayFromGameFile) {
13481             if (appData.timeDelay >= 0)
13482                 AutoPlayGameLoop();
13483         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13484             Reset(FALSE, TRUE);
13485             SendToICS(ics_prefix);
13486             SendToICS("refresh\n");
13487         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13488             ForwardInner(forwardMostMove);
13489         }
13490         pauseExamInvalid = FALSE;
13491     } else {
13492         switch (gameMode) {
13493           default:
13494             return;
13495           case IcsExamining:
13496             pauseExamForwardMostMove = forwardMostMove;
13497             pauseExamInvalid = FALSE;
13498             /* fall through */
13499           case IcsObserving:
13500           case IcsPlayingWhite:
13501           case IcsPlayingBlack:
13502             pausing = TRUE;
13503             ModeHighlight();
13504             return;
13505           case PlayFromGameFile:
13506             (void) StopLoadGameTimer();
13507             pausing = TRUE;
13508             ModeHighlight();
13509             break;
13510           case BeginningOfGame:
13511             if (appData.icsActive) return;
13512             /* else fall through */
13513           case MachinePlaysWhite:
13514           case MachinePlaysBlack:
13515           case TwoMachinesPlay:
13516             if (forwardMostMove == 0)
13517               return;           /* don't pause if no one has moved */
13518             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13519                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13520                 if(onMove->pause) {           // thinking engine can be paused
13521                     PauseEngine(onMove);      // do it
13522                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13523                         PauseEngine(onMove->other);
13524                     else
13525                         SendToProgram("easy\n", onMove->other);
13526                     StopClocks();
13527                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13528             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13529                 if(first.pause) {
13530                     PauseEngine(&first);
13531                     StopClocks();
13532                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13533             } else { // human on move, pause pondering by either method
13534                 if(first.pause)
13535                     PauseEngine(&first);
13536                 else if(appData.ponderNextMove)
13537                     SendToProgram("easy\n", &first);
13538                 StopClocks();
13539             }
13540             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13541           case AnalyzeMode:
13542             pausing = TRUE;
13543             ModeHighlight();
13544             break;
13545         }
13546     }
13547 }
13548
13549 void
13550 EditCommentEvent ()
13551 {
13552     char title[MSG_SIZ];
13553
13554     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13555       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13556     } else {
13557       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13558                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13559                parseList[currentMove - 1]);
13560     }
13561
13562     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13563 }
13564
13565
13566 void
13567 EditTagsEvent ()
13568 {
13569     char *tags = PGNTags(&gameInfo);
13570     bookUp = FALSE;
13571     EditTagsPopUp(tags, NULL);
13572     free(tags);
13573 }
13574
13575 void
13576 ToggleSecond ()
13577 {
13578   if(second.analyzing) {
13579     SendToProgram("exit\n", &second);
13580     second.analyzing = FALSE;
13581   } else {
13582     if (second.pr == NoProc) StartChessProgram(&second);
13583     InitChessProgram(&second, FALSE);
13584     FeedMovesToProgram(&second, currentMove);
13585
13586     SendToProgram("analyze\n", &second);
13587     second.analyzing = TRUE;
13588   }
13589 }
13590
13591 /* Toggle ShowThinking */
13592 void
13593 ToggleShowThinking()
13594 {
13595   appData.showThinking = !appData.showThinking;
13596   ShowThinkingEvent();
13597 }
13598
13599 int
13600 AnalyzeModeEvent ()
13601 {
13602     char buf[MSG_SIZ];
13603
13604     if (!first.analysisSupport) {
13605       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13606       DisplayError(buf, 0);
13607       return 0;
13608     }
13609     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13610     if (appData.icsActive) {
13611         if (gameMode != IcsObserving) {
13612           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13613             DisplayError(buf, 0);
13614             /* secure check */
13615             if (appData.icsEngineAnalyze) {
13616                 if (appData.debugMode)
13617                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13618                 ExitAnalyzeMode();
13619                 ModeHighlight();
13620             }
13621             return 0;
13622         }
13623         /* if enable, user wants to disable icsEngineAnalyze */
13624         if (appData.icsEngineAnalyze) {
13625                 ExitAnalyzeMode();
13626                 ModeHighlight();
13627                 return 0;
13628         }
13629         appData.icsEngineAnalyze = TRUE;
13630         if (appData.debugMode)
13631             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13632     }
13633
13634     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13635     if (appData.noChessProgram || gameMode == AnalyzeMode)
13636       return 0;
13637
13638     if (gameMode != AnalyzeFile) {
13639         if (!appData.icsEngineAnalyze) {
13640                EditGameEvent();
13641                if (gameMode != EditGame) return 0;
13642         }
13643         if (!appData.showThinking) ToggleShowThinking();
13644         ResurrectChessProgram();
13645         SendToProgram("analyze\n", &first);
13646         first.analyzing = TRUE;
13647         /*first.maybeThinking = TRUE;*/
13648         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13649         EngineOutputPopUp();
13650     }
13651     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13652     pausing = FALSE;
13653     ModeHighlight();
13654     SetGameInfo();
13655
13656     StartAnalysisClock();
13657     GetTimeMark(&lastNodeCountTime);
13658     lastNodeCount = 0;
13659     return 1;
13660 }
13661
13662 void
13663 AnalyzeFileEvent ()
13664 {
13665     if (appData.noChessProgram || gameMode == AnalyzeFile)
13666       return;
13667
13668     if (!first.analysisSupport) {
13669       char buf[MSG_SIZ];
13670       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13671       DisplayError(buf, 0);
13672       return;
13673     }
13674
13675     if (gameMode != AnalyzeMode) {
13676         keepInfo = 1; // mere annotating should not alter PGN tags
13677         EditGameEvent();
13678         keepInfo = 0;
13679         if (gameMode != EditGame) return;
13680         if (!appData.showThinking) ToggleShowThinking();
13681         ResurrectChessProgram();
13682         SendToProgram("analyze\n", &first);
13683         first.analyzing = TRUE;
13684         /*first.maybeThinking = TRUE;*/
13685         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13686         EngineOutputPopUp();
13687     }
13688     gameMode = AnalyzeFile;
13689     pausing = FALSE;
13690     ModeHighlight();
13691
13692     StartAnalysisClock();
13693     GetTimeMark(&lastNodeCountTime);
13694     lastNodeCount = 0;
13695     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13696     AnalysisPeriodicEvent(1);
13697 }
13698
13699 void
13700 MachineWhiteEvent ()
13701 {
13702     char buf[MSG_SIZ];
13703     char *bookHit = NULL;
13704
13705     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13706       return;
13707
13708
13709     if (gameMode == PlayFromGameFile ||
13710         gameMode == TwoMachinesPlay  ||
13711         gameMode == Training         ||
13712         gameMode == AnalyzeMode      ||
13713         gameMode == EndOfGame)
13714         EditGameEvent();
13715
13716     if (gameMode == EditPosition)
13717         EditPositionDone(TRUE);
13718
13719     if (!WhiteOnMove(currentMove)) {
13720         DisplayError(_("It is not White's turn"), 0);
13721         return;
13722     }
13723
13724     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13725       ExitAnalyzeMode();
13726
13727     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13728         gameMode == AnalyzeFile)
13729         TruncateGame();
13730
13731     ResurrectChessProgram();    /* in case it isn't running */
13732     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13733         gameMode = MachinePlaysWhite;
13734         ResetClocks();
13735     } else
13736     gameMode = MachinePlaysWhite;
13737     pausing = FALSE;
13738     ModeHighlight();
13739     SetGameInfo();
13740     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13741     DisplayTitle(buf);
13742     if (first.sendName) {
13743       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13744       SendToProgram(buf, &first);
13745     }
13746     if (first.sendTime) {
13747       if (first.useColors) {
13748         SendToProgram("black\n", &first); /*gnu kludge*/
13749       }
13750       SendTimeRemaining(&first, TRUE);
13751     }
13752     if (first.useColors) {
13753       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13754     }
13755     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13756     SetMachineThinkingEnables();
13757     first.maybeThinking = TRUE;
13758     StartClocks();
13759     firstMove = FALSE;
13760
13761     if (appData.autoFlipView && !flipView) {
13762       flipView = !flipView;
13763       DrawPosition(FALSE, NULL);
13764       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13765     }
13766
13767     if(bookHit) { // [HGM] book: simulate book reply
13768         static char bookMove[MSG_SIZ]; // a bit generous?
13769
13770         programStats.nodes = programStats.depth = programStats.time =
13771         programStats.score = programStats.got_only_move = 0;
13772         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13773
13774         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13775         strcat(bookMove, bookHit);
13776         HandleMachineMove(bookMove, &first);
13777     }
13778 }
13779
13780 void
13781 MachineBlackEvent ()
13782 {
13783   char buf[MSG_SIZ];
13784   char *bookHit = NULL;
13785
13786     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13787         return;
13788
13789
13790     if (gameMode == PlayFromGameFile ||
13791         gameMode == TwoMachinesPlay  ||
13792         gameMode == Training         ||
13793         gameMode == AnalyzeMode      ||
13794         gameMode == EndOfGame)
13795         EditGameEvent();
13796
13797     if (gameMode == EditPosition)
13798         EditPositionDone(TRUE);
13799
13800     if (WhiteOnMove(currentMove)) {
13801         DisplayError(_("It is not Black's turn"), 0);
13802         return;
13803     }
13804
13805     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13806       ExitAnalyzeMode();
13807
13808     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13809         gameMode == AnalyzeFile)
13810         TruncateGame();
13811
13812     ResurrectChessProgram();    /* in case it isn't running */
13813     gameMode = MachinePlaysBlack;
13814     pausing = FALSE;
13815     ModeHighlight();
13816     SetGameInfo();
13817     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13818     DisplayTitle(buf);
13819     if (first.sendName) {
13820       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13821       SendToProgram(buf, &first);
13822     }
13823     if (first.sendTime) {
13824       if (first.useColors) {
13825         SendToProgram("white\n", &first); /*gnu kludge*/
13826       }
13827       SendTimeRemaining(&first, FALSE);
13828     }
13829     if (first.useColors) {
13830       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13831     }
13832     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13833     SetMachineThinkingEnables();
13834     first.maybeThinking = TRUE;
13835     StartClocks();
13836
13837     if (appData.autoFlipView && flipView) {
13838       flipView = !flipView;
13839       DrawPosition(FALSE, NULL);
13840       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13841     }
13842     if(bookHit) { // [HGM] book: simulate book reply
13843         static char bookMove[MSG_SIZ]; // a bit generous?
13844
13845         programStats.nodes = programStats.depth = programStats.time =
13846         programStats.score = programStats.got_only_move = 0;
13847         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13848
13849         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13850         strcat(bookMove, bookHit);
13851         HandleMachineMove(bookMove, &first);
13852     }
13853 }
13854
13855
13856 void
13857 DisplayTwoMachinesTitle ()
13858 {
13859     char buf[MSG_SIZ];
13860     if (appData.matchGames > 0) {
13861         if(appData.tourneyFile[0]) {
13862           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13863                    gameInfo.white, _("vs."), gameInfo.black,
13864                    nextGame+1, appData.matchGames+1,
13865                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13866         } else
13867         if (first.twoMachinesColor[0] == 'w') {
13868           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13869                    gameInfo.white, _("vs."),  gameInfo.black,
13870                    first.matchWins, second.matchWins,
13871                    matchGame - 1 - (first.matchWins + second.matchWins));
13872         } else {
13873           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13874                    gameInfo.white, _("vs."), gameInfo.black,
13875                    second.matchWins, first.matchWins,
13876                    matchGame - 1 - (first.matchWins + second.matchWins));
13877         }
13878     } else {
13879       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13880     }
13881     DisplayTitle(buf);
13882 }
13883
13884 void
13885 SettingsMenuIfReady ()
13886 {
13887   if (second.lastPing != second.lastPong) {
13888     DisplayMessage("", _("Waiting for second chess program"));
13889     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13890     return;
13891   }
13892   ThawUI();
13893   DisplayMessage("", "");
13894   SettingsPopUp(&second);
13895 }
13896
13897 int
13898 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13899 {
13900     char buf[MSG_SIZ];
13901     if (cps->pr == NoProc) {
13902         StartChessProgram(cps);
13903         if (cps->protocolVersion == 1) {
13904           retry();
13905         } else {
13906           /* kludge: allow timeout for initial "feature" command */
13907           FreezeUI();
13908           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13909           DisplayMessage("", buf);
13910           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13911         }
13912         return 1;
13913     }
13914     return 0;
13915 }
13916
13917 void
13918 TwoMachinesEvent P((void))
13919 {
13920     int i;
13921     char buf[MSG_SIZ];
13922     ChessProgramState *onmove;
13923     char *bookHit = NULL;
13924     static int stalling = 0;
13925     TimeMark now;
13926     long wait;
13927
13928     if (appData.noChessProgram) return;
13929
13930     switch (gameMode) {
13931       case TwoMachinesPlay:
13932         return;
13933       case MachinePlaysWhite:
13934       case MachinePlaysBlack:
13935         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13936             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13937             return;
13938         }
13939         /* fall through */
13940       case BeginningOfGame:
13941       case PlayFromGameFile:
13942       case EndOfGame:
13943         EditGameEvent();
13944         if (gameMode != EditGame) return;
13945         break;
13946       case EditPosition:
13947         EditPositionDone(TRUE);
13948         break;
13949       case AnalyzeMode:
13950       case AnalyzeFile:
13951         ExitAnalyzeMode();
13952         break;
13953       case EditGame:
13954       default:
13955         break;
13956     }
13957
13958 //    forwardMostMove = currentMove;
13959     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13960
13961     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13962
13963     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13964     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13965       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13966       return;
13967     }
13968
13969     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13970         DisplayError("second engine does not play this", 0);
13971         return;
13972     }
13973
13974     if(!stalling) {
13975       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13976       SendToProgram("force\n", &second);
13977       stalling = 1;
13978       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13979       return;
13980     }
13981     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13982     if(appData.matchPause>10000 || appData.matchPause<10)
13983                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13984     wait = SubtractTimeMarks(&now, &pauseStart);
13985     if(wait < appData.matchPause) {
13986         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13987         return;
13988     }
13989     // we are now committed to starting the game
13990     stalling = 0;
13991     DisplayMessage("", "");
13992     if (startedFromSetupPosition) {
13993         SendBoard(&second, backwardMostMove);
13994     if (appData.debugMode) {
13995         fprintf(debugFP, "Two Machines\n");
13996     }
13997     }
13998     for (i = backwardMostMove; i < forwardMostMove; i++) {
13999         SendMoveToProgram(i, &second);
14000     }
14001
14002     gameMode = TwoMachinesPlay;
14003     pausing = FALSE;
14004     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14005     SetGameInfo();
14006     DisplayTwoMachinesTitle();
14007     firstMove = TRUE;
14008     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14009         onmove = &first;
14010     } else {
14011         onmove = &second;
14012     }
14013     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14014     SendToProgram(first.computerString, &first);
14015     if (first.sendName) {
14016       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14017       SendToProgram(buf, &first);
14018     }
14019     SendToProgram(second.computerString, &second);
14020     if (second.sendName) {
14021       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14022       SendToProgram(buf, &second);
14023     }
14024
14025     ResetClocks();
14026     if (!first.sendTime || !second.sendTime) {
14027         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14028         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14029     }
14030     if (onmove->sendTime) {
14031       if (onmove->useColors) {
14032         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14033       }
14034       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14035     }
14036     if (onmove->useColors) {
14037       SendToProgram(onmove->twoMachinesColor, onmove);
14038     }
14039     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14040 //    SendToProgram("go\n", onmove);
14041     onmove->maybeThinking = TRUE;
14042     SetMachineThinkingEnables();
14043
14044     StartClocks();
14045
14046     if(bookHit) { // [HGM] book: simulate book reply
14047         static char bookMove[MSG_SIZ]; // a bit generous?
14048
14049         programStats.nodes = programStats.depth = programStats.time =
14050         programStats.score = programStats.got_only_move = 0;
14051         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14052
14053         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14054         strcat(bookMove, bookHit);
14055         savedMessage = bookMove; // args for deferred call
14056         savedState = onmove;
14057         ScheduleDelayedEvent(DeferredBookMove, 1);
14058     }
14059 }
14060
14061 void
14062 TrainingEvent ()
14063 {
14064     if (gameMode == Training) {
14065       SetTrainingModeOff();
14066       gameMode = PlayFromGameFile;
14067       DisplayMessage("", _("Training mode off"));
14068     } else {
14069       gameMode = Training;
14070       animateTraining = appData.animate;
14071
14072       /* make sure we are not already at the end of the game */
14073       if (currentMove < forwardMostMove) {
14074         SetTrainingModeOn();
14075         DisplayMessage("", _("Training mode on"));
14076       } else {
14077         gameMode = PlayFromGameFile;
14078         DisplayError(_("Already at end of game"), 0);
14079       }
14080     }
14081     ModeHighlight();
14082 }
14083
14084 void
14085 IcsClientEvent ()
14086 {
14087     if (!appData.icsActive) return;
14088     switch (gameMode) {
14089       case IcsPlayingWhite:
14090       case IcsPlayingBlack:
14091       case IcsObserving:
14092       case IcsIdle:
14093       case BeginningOfGame:
14094       case IcsExamining:
14095         return;
14096
14097       case EditGame:
14098         break;
14099
14100       case EditPosition:
14101         EditPositionDone(TRUE);
14102         break;
14103
14104       case AnalyzeMode:
14105       case AnalyzeFile:
14106         ExitAnalyzeMode();
14107         break;
14108
14109       default:
14110         EditGameEvent();
14111         break;
14112     }
14113
14114     gameMode = IcsIdle;
14115     ModeHighlight();
14116     return;
14117 }
14118
14119 void
14120 EditGameEvent ()
14121 {
14122     int i;
14123
14124     switch (gameMode) {
14125       case Training:
14126         SetTrainingModeOff();
14127         break;
14128       case MachinePlaysWhite:
14129       case MachinePlaysBlack:
14130       case BeginningOfGame:
14131         SendToProgram("force\n", &first);
14132         SetUserThinkingEnables();
14133         break;
14134       case PlayFromGameFile:
14135         (void) StopLoadGameTimer();
14136         if (gameFileFP != NULL) {
14137             gameFileFP = NULL;
14138         }
14139         break;
14140       case EditPosition:
14141         EditPositionDone(TRUE);
14142         break;
14143       case AnalyzeMode:
14144       case AnalyzeFile:
14145         ExitAnalyzeMode();
14146         SendToProgram("force\n", &first);
14147         break;
14148       case TwoMachinesPlay:
14149         GameEnds(EndOfFile, NULL, GE_PLAYER);
14150         ResurrectChessProgram();
14151         SetUserThinkingEnables();
14152         break;
14153       case EndOfGame:
14154         ResurrectChessProgram();
14155         break;
14156       case IcsPlayingBlack:
14157       case IcsPlayingWhite:
14158         DisplayError(_("Warning: You are still playing a game"), 0);
14159         break;
14160       case IcsObserving:
14161         DisplayError(_("Warning: You are still observing a game"), 0);
14162         break;
14163       case IcsExamining:
14164         DisplayError(_("Warning: You are still examining a game"), 0);
14165         break;
14166       case IcsIdle:
14167         break;
14168       case EditGame:
14169       default:
14170         return;
14171     }
14172
14173     pausing = FALSE;
14174     StopClocks();
14175     first.offeredDraw = second.offeredDraw = 0;
14176
14177     if (gameMode == PlayFromGameFile) {
14178         whiteTimeRemaining = timeRemaining[0][currentMove];
14179         blackTimeRemaining = timeRemaining[1][currentMove];
14180         DisplayTitle("");
14181     }
14182
14183     if (gameMode == MachinePlaysWhite ||
14184         gameMode == MachinePlaysBlack ||
14185         gameMode == TwoMachinesPlay ||
14186         gameMode == EndOfGame) {
14187         i = forwardMostMove;
14188         while (i > currentMove) {
14189             SendToProgram("undo\n", &first);
14190             i--;
14191         }
14192         if(!adjustedClock) {
14193         whiteTimeRemaining = timeRemaining[0][currentMove];
14194         blackTimeRemaining = timeRemaining[1][currentMove];
14195         DisplayBothClocks();
14196         }
14197         if (whiteFlag || blackFlag) {
14198             whiteFlag = blackFlag = 0;
14199         }
14200         DisplayTitle("");
14201     }
14202
14203     gameMode = EditGame;
14204     ModeHighlight();
14205     SetGameInfo();
14206 }
14207
14208
14209 void
14210 EditPositionEvent ()
14211 {
14212     if (gameMode == EditPosition) {
14213         EditGameEvent();
14214         return;
14215     }
14216
14217     EditGameEvent();
14218     if (gameMode != EditGame) return;
14219
14220     gameMode = EditPosition;
14221     ModeHighlight();
14222     SetGameInfo();
14223     if (currentMove > 0)
14224       CopyBoard(boards[0], boards[currentMove]);
14225
14226     blackPlaysFirst = !WhiteOnMove(currentMove);
14227     ResetClocks();
14228     currentMove = forwardMostMove = backwardMostMove = 0;
14229     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14230     DisplayMove(-1);
14231     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14232 }
14233
14234 void
14235 ExitAnalyzeMode ()
14236 {
14237     /* [DM] icsEngineAnalyze - possible call from other functions */
14238     if (appData.icsEngineAnalyze) {
14239         appData.icsEngineAnalyze = FALSE;
14240
14241         DisplayMessage("",_("Close ICS engine analyze..."));
14242     }
14243     if (first.analysisSupport && first.analyzing) {
14244       SendToBoth("exit\n");
14245       first.analyzing = second.analyzing = FALSE;
14246     }
14247     thinkOutput[0] = NULLCHAR;
14248 }
14249
14250 void
14251 EditPositionDone (Boolean fakeRights)
14252 {
14253     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14254
14255     startedFromSetupPosition = TRUE;
14256     InitChessProgram(&first, FALSE);
14257     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14258       boards[0][EP_STATUS] = EP_NONE;
14259       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14260       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14261         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14262         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14263       } else boards[0][CASTLING][2] = NoRights;
14264       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14265         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14266         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14267       } else boards[0][CASTLING][5] = NoRights;
14268       if(gameInfo.variant == VariantSChess) {
14269         int i;
14270         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14271           boards[0][VIRGIN][i] = 0;
14272           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14273           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14274         }
14275       }
14276     }
14277     SendToProgram("force\n", &first);
14278     if (blackPlaysFirst) {
14279         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14280         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14281         currentMove = forwardMostMove = backwardMostMove = 1;
14282         CopyBoard(boards[1], boards[0]);
14283     } else {
14284         currentMove = forwardMostMove = backwardMostMove = 0;
14285     }
14286     SendBoard(&first, forwardMostMove);
14287     if (appData.debugMode) {
14288         fprintf(debugFP, "EditPosDone\n");
14289     }
14290     DisplayTitle("");
14291     DisplayMessage("", "");
14292     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14293     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14294     gameMode = EditGame;
14295     ModeHighlight();
14296     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14297     ClearHighlights(); /* [AS] */
14298 }
14299
14300 /* Pause for `ms' milliseconds */
14301 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14302 void
14303 TimeDelay (long ms)
14304 {
14305     TimeMark m1, m2;
14306
14307     GetTimeMark(&m1);
14308     do {
14309         GetTimeMark(&m2);
14310     } while (SubtractTimeMarks(&m2, &m1) < ms);
14311 }
14312
14313 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14314 void
14315 SendMultiLineToICS (char *buf)
14316 {
14317     char temp[MSG_SIZ+1], *p;
14318     int len;
14319
14320     len = strlen(buf);
14321     if (len > MSG_SIZ)
14322       len = MSG_SIZ;
14323
14324     strncpy(temp, buf, len);
14325     temp[len] = 0;
14326
14327     p = temp;
14328     while (*p) {
14329         if (*p == '\n' || *p == '\r')
14330           *p = ' ';
14331         ++p;
14332     }
14333
14334     strcat(temp, "\n");
14335     SendToICS(temp);
14336     SendToPlayer(temp, strlen(temp));
14337 }
14338
14339 void
14340 SetWhiteToPlayEvent ()
14341 {
14342     if (gameMode == EditPosition) {
14343         blackPlaysFirst = FALSE;
14344         DisplayBothClocks();    /* works because currentMove is 0 */
14345     } else if (gameMode == IcsExamining) {
14346         SendToICS(ics_prefix);
14347         SendToICS("tomove white\n");
14348     }
14349 }
14350
14351 void
14352 SetBlackToPlayEvent ()
14353 {
14354     if (gameMode == EditPosition) {
14355         blackPlaysFirst = TRUE;
14356         currentMove = 1;        /* kludge */
14357         DisplayBothClocks();
14358         currentMove = 0;
14359     } else if (gameMode == IcsExamining) {
14360         SendToICS(ics_prefix);
14361         SendToICS("tomove black\n");
14362     }
14363 }
14364
14365 void
14366 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14367 {
14368     char buf[MSG_SIZ];
14369     ChessSquare piece = boards[0][y][x];
14370
14371     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14372
14373     switch (selection) {
14374       case ClearBoard:
14375         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14376             SendToICS(ics_prefix);
14377             SendToICS("bsetup clear\n");
14378         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14379             SendToICS(ics_prefix);
14380             SendToICS("clearboard\n");
14381         } else {
14382             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14383                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14384                 for (y = 0; y < BOARD_HEIGHT; y++) {
14385                     if (gameMode == IcsExamining) {
14386                         if (boards[currentMove][y][x] != EmptySquare) {
14387                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14388                                     AAA + x, ONE + y);
14389                             SendToICS(buf);
14390                         }
14391                     } else {
14392                         boards[0][y][x] = p;
14393                     }
14394                 }
14395             }
14396         }
14397         if (gameMode == EditPosition) {
14398             DrawPosition(FALSE, boards[0]);
14399         }
14400         break;
14401
14402       case WhitePlay:
14403         SetWhiteToPlayEvent();
14404         break;
14405
14406       case BlackPlay:
14407         SetBlackToPlayEvent();
14408         break;
14409
14410       case EmptySquare:
14411         if (gameMode == IcsExamining) {
14412             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14413             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14414             SendToICS(buf);
14415         } else {
14416             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14417                 if(x == BOARD_LEFT-2) {
14418                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14419                     boards[0][y][1] = 0;
14420                 } else
14421                 if(x == BOARD_RGHT+1) {
14422                     if(y >= gameInfo.holdingsSize) break;
14423                     boards[0][y][BOARD_WIDTH-2] = 0;
14424                 } else break;
14425             }
14426             boards[0][y][x] = EmptySquare;
14427             DrawPosition(FALSE, boards[0]);
14428         }
14429         break;
14430
14431       case PromotePiece:
14432         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14433            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14434             selection = (ChessSquare) (PROMOTED piece);
14435         } else if(piece == EmptySquare) selection = WhiteSilver;
14436         else selection = (ChessSquare)((int)piece - 1);
14437         goto defaultlabel;
14438
14439       case DemotePiece:
14440         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14441            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14442             selection = (ChessSquare) (DEMOTED piece);
14443         } else if(piece == EmptySquare) selection = BlackSilver;
14444         else selection = (ChessSquare)((int)piece + 1);
14445         goto defaultlabel;
14446
14447       case WhiteQueen:
14448       case BlackQueen:
14449         if(gameInfo.variant == VariantShatranj ||
14450            gameInfo.variant == VariantXiangqi  ||
14451            gameInfo.variant == VariantCourier  ||
14452            gameInfo.variant == VariantMakruk     )
14453             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14454         goto defaultlabel;
14455
14456       case WhiteKing:
14457       case BlackKing:
14458         if(gameInfo.variant == VariantXiangqi)
14459             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14460         if(gameInfo.variant == VariantKnightmate)
14461             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14462       default:
14463         defaultlabel:
14464         if (gameMode == IcsExamining) {
14465             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14466             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14467                      PieceToChar(selection), AAA + x, ONE + y);
14468             SendToICS(buf);
14469         } else {
14470             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14471                 int n;
14472                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14473                     n = PieceToNumber(selection - BlackPawn);
14474                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14475                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14476                     boards[0][BOARD_HEIGHT-1-n][1]++;
14477                 } else
14478                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14479                     n = PieceToNumber(selection);
14480                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14481                     boards[0][n][BOARD_WIDTH-1] = selection;
14482                     boards[0][n][BOARD_WIDTH-2]++;
14483                 }
14484             } else
14485             boards[0][y][x] = selection;
14486             DrawPosition(TRUE, boards[0]);
14487             ClearHighlights();
14488             fromX = fromY = -1;
14489         }
14490         break;
14491     }
14492 }
14493
14494
14495 void
14496 DropMenuEvent (ChessSquare selection, int x, int y)
14497 {
14498     ChessMove moveType;
14499
14500     switch (gameMode) {
14501       case IcsPlayingWhite:
14502       case MachinePlaysBlack:
14503         if (!WhiteOnMove(currentMove)) {
14504             DisplayMoveError(_("It is Black's turn"));
14505             return;
14506         }
14507         moveType = WhiteDrop;
14508         break;
14509       case IcsPlayingBlack:
14510       case MachinePlaysWhite:
14511         if (WhiteOnMove(currentMove)) {
14512             DisplayMoveError(_("It is White's turn"));
14513             return;
14514         }
14515         moveType = BlackDrop;
14516         break;
14517       case EditGame:
14518         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14519         break;
14520       default:
14521         return;
14522     }
14523
14524     if (moveType == BlackDrop && selection < BlackPawn) {
14525       selection = (ChessSquare) ((int) selection
14526                                  + (int) BlackPawn - (int) WhitePawn);
14527     }
14528     if (boards[currentMove][y][x] != EmptySquare) {
14529         DisplayMoveError(_("That square is occupied"));
14530         return;
14531     }
14532
14533     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14534 }
14535
14536 void
14537 AcceptEvent ()
14538 {
14539     /* Accept a pending offer of any kind from opponent */
14540
14541     if (appData.icsActive) {
14542         SendToICS(ics_prefix);
14543         SendToICS("accept\n");
14544     } else if (cmailMsgLoaded) {
14545         if (currentMove == cmailOldMove &&
14546             commentList[cmailOldMove] != NULL &&
14547             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14548                    "Black offers a draw" : "White offers a draw")) {
14549             TruncateGame();
14550             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14551             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14552         } else {
14553             DisplayError(_("There is no pending offer on this move"), 0);
14554             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14555         }
14556     } else {
14557         /* Not used for offers from chess program */
14558     }
14559 }
14560
14561 void
14562 DeclineEvent ()
14563 {
14564     /* Decline a pending offer of any kind from opponent */
14565
14566     if (appData.icsActive) {
14567         SendToICS(ics_prefix);
14568         SendToICS("decline\n");
14569     } else if (cmailMsgLoaded) {
14570         if (currentMove == cmailOldMove &&
14571             commentList[cmailOldMove] != NULL &&
14572             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14573                    "Black offers a draw" : "White offers a draw")) {
14574 #ifdef NOTDEF
14575             AppendComment(cmailOldMove, "Draw declined", TRUE);
14576             DisplayComment(cmailOldMove - 1, "Draw declined");
14577 #endif /*NOTDEF*/
14578         } else {
14579             DisplayError(_("There is no pending offer on this move"), 0);
14580         }
14581     } else {
14582         /* Not used for offers from chess program */
14583     }
14584 }
14585
14586 void
14587 RematchEvent ()
14588 {
14589     /* Issue ICS rematch command */
14590     if (appData.icsActive) {
14591         SendToICS(ics_prefix);
14592         SendToICS("rematch\n");
14593     }
14594 }
14595
14596 void
14597 CallFlagEvent ()
14598 {
14599     /* Call your opponent's flag (claim a win on time) */
14600     if (appData.icsActive) {
14601         SendToICS(ics_prefix);
14602         SendToICS("flag\n");
14603     } else {
14604         switch (gameMode) {
14605           default:
14606             return;
14607           case MachinePlaysWhite:
14608             if (whiteFlag) {
14609                 if (blackFlag)
14610                   GameEnds(GameIsDrawn, "Both players ran out of time",
14611                            GE_PLAYER);
14612                 else
14613                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14614             } else {
14615                 DisplayError(_("Your opponent is not out of time"), 0);
14616             }
14617             break;
14618           case MachinePlaysBlack:
14619             if (blackFlag) {
14620                 if (whiteFlag)
14621                   GameEnds(GameIsDrawn, "Both players ran out of time",
14622                            GE_PLAYER);
14623                 else
14624                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14625             } else {
14626                 DisplayError(_("Your opponent is not out of time"), 0);
14627             }
14628             break;
14629         }
14630     }
14631 }
14632
14633 void
14634 ClockClick (int which)
14635 {       // [HGM] code moved to back-end from winboard.c
14636         if(which) { // black clock
14637           if (gameMode == EditPosition || gameMode == IcsExamining) {
14638             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14639             SetBlackToPlayEvent();
14640           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14641           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14642           } else if (shiftKey) {
14643             AdjustClock(which, -1);
14644           } else if (gameMode == IcsPlayingWhite ||
14645                      gameMode == MachinePlaysBlack) {
14646             CallFlagEvent();
14647           }
14648         } else { // white clock
14649           if (gameMode == EditPosition || gameMode == IcsExamining) {
14650             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14651             SetWhiteToPlayEvent();
14652           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14653           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14654           } else if (shiftKey) {
14655             AdjustClock(which, -1);
14656           } else if (gameMode == IcsPlayingBlack ||
14657                    gameMode == MachinePlaysWhite) {
14658             CallFlagEvent();
14659           }
14660         }
14661 }
14662
14663 void
14664 DrawEvent ()
14665 {
14666     /* Offer draw or accept pending draw offer from opponent */
14667
14668     if (appData.icsActive) {
14669         /* Note: tournament rules require draw offers to be
14670            made after you make your move but before you punch
14671            your clock.  Currently ICS doesn't let you do that;
14672            instead, you immediately punch your clock after making
14673            a move, but you can offer a draw at any time. */
14674
14675         SendToICS(ics_prefix);
14676         SendToICS("draw\n");
14677         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14678     } else if (cmailMsgLoaded) {
14679         if (currentMove == cmailOldMove &&
14680             commentList[cmailOldMove] != NULL &&
14681             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14682                    "Black offers a draw" : "White offers a draw")) {
14683             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14684             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14685         } else if (currentMove == cmailOldMove + 1) {
14686             char *offer = WhiteOnMove(cmailOldMove) ?
14687               "White offers a draw" : "Black offers a draw";
14688             AppendComment(currentMove, offer, TRUE);
14689             DisplayComment(currentMove - 1, offer);
14690             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14691         } else {
14692             DisplayError(_("You must make your move before offering a draw"), 0);
14693             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14694         }
14695     } else if (first.offeredDraw) {
14696         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14697     } else {
14698         if (first.sendDrawOffers) {
14699             SendToProgram("draw\n", &first);
14700             userOfferedDraw = TRUE;
14701         }
14702     }
14703 }
14704
14705 void
14706 AdjournEvent ()
14707 {
14708     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14709
14710     if (appData.icsActive) {
14711         SendToICS(ics_prefix);
14712         SendToICS("adjourn\n");
14713     } else {
14714         /* Currently GNU Chess doesn't offer or accept Adjourns */
14715     }
14716 }
14717
14718
14719 void
14720 AbortEvent ()
14721 {
14722     /* Offer Abort or accept pending Abort offer from opponent */
14723
14724     if (appData.icsActive) {
14725         SendToICS(ics_prefix);
14726         SendToICS("abort\n");
14727     } else {
14728         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14729     }
14730 }
14731
14732 void
14733 ResignEvent ()
14734 {
14735     /* Resign.  You can do this even if it's not your turn. */
14736
14737     if (appData.icsActive) {
14738         SendToICS(ics_prefix);
14739         SendToICS("resign\n");
14740     } else {
14741         switch (gameMode) {
14742           case MachinePlaysWhite:
14743             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14744             break;
14745           case MachinePlaysBlack:
14746             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14747             break;
14748           case EditGame:
14749             if (cmailMsgLoaded) {
14750                 TruncateGame();
14751                 if (WhiteOnMove(cmailOldMove)) {
14752                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14753                 } else {
14754                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14755                 }
14756                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14757             }
14758             break;
14759           default:
14760             break;
14761         }
14762     }
14763 }
14764
14765
14766 void
14767 StopObservingEvent ()
14768 {
14769     /* Stop observing current games */
14770     SendToICS(ics_prefix);
14771     SendToICS("unobserve\n");
14772 }
14773
14774 void
14775 StopExaminingEvent ()
14776 {
14777     /* Stop observing current game */
14778     SendToICS(ics_prefix);
14779     SendToICS("unexamine\n");
14780 }
14781
14782 void
14783 ForwardInner (int target)
14784 {
14785     int limit; int oldSeekGraphUp = seekGraphUp;
14786
14787     if (appData.debugMode)
14788         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14789                 target, currentMove, forwardMostMove);
14790
14791     if (gameMode == EditPosition)
14792       return;
14793
14794     seekGraphUp = FALSE;
14795     MarkTargetSquares(1);
14796
14797     if (gameMode == PlayFromGameFile && !pausing)
14798       PauseEvent();
14799
14800     if (gameMode == IcsExamining && pausing)
14801       limit = pauseExamForwardMostMove;
14802     else
14803       limit = forwardMostMove;
14804
14805     if (target > limit) target = limit;
14806
14807     if (target > 0 && moveList[target - 1][0]) {
14808         int fromX, fromY, toX, toY;
14809         toX = moveList[target - 1][2] - AAA;
14810         toY = moveList[target - 1][3] - ONE;
14811         if (moveList[target - 1][1] == '@') {
14812             if (appData.highlightLastMove) {
14813                 SetHighlights(-1, -1, toX, toY);
14814             }
14815         } else {
14816             fromX = moveList[target - 1][0] - AAA;
14817             fromY = moveList[target - 1][1] - ONE;
14818             if (target == currentMove + 1) {
14819                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14820             }
14821             if (appData.highlightLastMove) {
14822                 SetHighlights(fromX, fromY, toX, toY);
14823             }
14824         }
14825     }
14826     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14827         gameMode == Training || gameMode == PlayFromGameFile ||
14828         gameMode == AnalyzeFile) {
14829         while (currentMove < target) {
14830             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14831             SendMoveToProgram(currentMove++, &first);
14832         }
14833     } else {
14834         currentMove = target;
14835     }
14836
14837     if (gameMode == EditGame || gameMode == EndOfGame) {
14838         whiteTimeRemaining = timeRemaining[0][currentMove];
14839         blackTimeRemaining = timeRemaining[1][currentMove];
14840     }
14841     DisplayBothClocks();
14842     DisplayMove(currentMove - 1);
14843     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14844     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14845     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14846         DisplayComment(currentMove - 1, commentList[currentMove]);
14847     }
14848     ClearMap(); // [HGM] exclude: invalidate map
14849 }
14850
14851
14852 void
14853 ForwardEvent ()
14854 {
14855     if (gameMode == IcsExamining && !pausing) {
14856         SendToICS(ics_prefix);
14857         SendToICS("forward\n");
14858     } else {
14859         ForwardInner(currentMove + 1);
14860     }
14861 }
14862
14863 void
14864 ToEndEvent ()
14865 {
14866     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14867         /* to optimze, we temporarily turn off analysis mode while we feed
14868          * the remaining moves to the engine. Otherwise we get analysis output
14869          * after each move.
14870          */
14871         if (first.analysisSupport) {
14872           SendToProgram("exit\nforce\n", &first);
14873           first.analyzing = FALSE;
14874         }
14875     }
14876
14877     if (gameMode == IcsExamining && !pausing) {
14878         SendToICS(ics_prefix);
14879         SendToICS("forward 999999\n");
14880     } else {
14881         ForwardInner(forwardMostMove);
14882     }
14883
14884     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14885         /* we have fed all the moves, so reactivate analysis mode */
14886         SendToProgram("analyze\n", &first);
14887         first.analyzing = TRUE;
14888         /*first.maybeThinking = TRUE;*/
14889         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14890     }
14891 }
14892
14893 void
14894 BackwardInner (int target)
14895 {
14896     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14897
14898     if (appData.debugMode)
14899         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14900                 target, currentMove, forwardMostMove);
14901
14902     if (gameMode == EditPosition) return;
14903     seekGraphUp = FALSE;
14904     MarkTargetSquares(1);
14905     if (currentMove <= backwardMostMove) {
14906         ClearHighlights();
14907         DrawPosition(full_redraw, boards[currentMove]);
14908         return;
14909     }
14910     if (gameMode == PlayFromGameFile && !pausing)
14911       PauseEvent();
14912
14913     if (moveList[target][0]) {
14914         int fromX, fromY, toX, toY;
14915         toX = moveList[target][2] - AAA;
14916         toY = moveList[target][3] - ONE;
14917         if (moveList[target][1] == '@') {
14918             if (appData.highlightLastMove) {
14919                 SetHighlights(-1, -1, toX, toY);
14920             }
14921         } else {
14922             fromX = moveList[target][0] - AAA;
14923             fromY = moveList[target][1] - ONE;
14924             if (target == currentMove - 1) {
14925                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14926             }
14927             if (appData.highlightLastMove) {
14928                 SetHighlights(fromX, fromY, toX, toY);
14929             }
14930         }
14931     }
14932     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14933         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14934         while (currentMove > target) {
14935             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14936                 // null move cannot be undone. Reload program with move history before it.
14937                 int i;
14938                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14939                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14940                 }
14941                 SendBoard(&first, i);
14942               if(second.analyzing) SendBoard(&second, i);
14943                 for(currentMove=i; currentMove<target; currentMove++) {
14944                     SendMoveToProgram(currentMove, &first);
14945                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14946                 }
14947                 break;
14948             }
14949             SendToBoth("undo\n");
14950             currentMove--;
14951         }
14952     } else {
14953         currentMove = target;
14954     }
14955
14956     if (gameMode == EditGame || gameMode == EndOfGame) {
14957         whiteTimeRemaining = timeRemaining[0][currentMove];
14958         blackTimeRemaining = timeRemaining[1][currentMove];
14959     }
14960     DisplayBothClocks();
14961     DisplayMove(currentMove - 1);
14962     DrawPosition(full_redraw, boards[currentMove]);
14963     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14964     // [HGM] PV info: routine tests if comment empty
14965     DisplayComment(currentMove - 1, commentList[currentMove]);
14966     ClearMap(); // [HGM] exclude: invalidate map
14967 }
14968
14969 void
14970 BackwardEvent ()
14971 {
14972     if (gameMode == IcsExamining && !pausing) {
14973         SendToICS(ics_prefix);
14974         SendToICS("backward\n");
14975     } else {
14976         BackwardInner(currentMove - 1);
14977     }
14978 }
14979
14980 void
14981 ToStartEvent ()
14982 {
14983     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14984         /* to optimize, we temporarily turn off analysis mode while we undo
14985          * all the moves. Otherwise we get analysis output after each undo.
14986          */
14987         if (first.analysisSupport) {
14988           SendToProgram("exit\nforce\n", &first);
14989           first.analyzing = FALSE;
14990         }
14991     }
14992
14993     if (gameMode == IcsExamining && !pausing) {
14994         SendToICS(ics_prefix);
14995         SendToICS("backward 999999\n");
14996     } else {
14997         BackwardInner(backwardMostMove);
14998     }
14999
15000     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15001         /* we have fed all the moves, so reactivate analysis mode */
15002         SendToProgram("analyze\n", &first);
15003         first.analyzing = TRUE;
15004         /*first.maybeThinking = TRUE;*/
15005         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15006     }
15007 }
15008
15009 void
15010 ToNrEvent (int to)
15011 {
15012   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15013   if (to >= forwardMostMove) to = forwardMostMove;
15014   if (to <= backwardMostMove) to = backwardMostMove;
15015   if (to < currentMove) {
15016     BackwardInner(to);
15017   } else {
15018     ForwardInner(to);
15019   }
15020 }
15021
15022 void
15023 RevertEvent (Boolean annotate)
15024 {
15025     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15026         return;
15027     }
15028     if (gameMode != IcsExamining) {
15029         DisplayError(_("You are not examining a game"), 0);
15030         return;
15031     }
15032     if (pausing) {
15033         DisplayError(_("You can't revert while pausing"), 0);
15034         return;
15035     }
15036     SendToICS(ics_prefix);
15037     SendToICS("revert\n");
15038 }
15039
15040 void
15041 RetractMoveEvent ()
15042 {
15043     switch (gameMode) {
15044       case MachinePlaysWhite:
15045       case MachinePlaysBlack:
15046         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15047             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15048             return;
15049         }
15050         if (forwardMostMove < 2) return;
15051         currentMove = forwardMostMove = forwardMostMove - 2;
15052         whiteTimeRemaining = timeRemaining[0][currentMove];
15053         blackTimeRemaining = timeRemaining[1][currentMove];
15054         DisplayBothClocks();
15055         DisplayMove(currentMove - 1);
15056         ClearHighlights();/*!! could figure this out*/
15057         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15058         SendToProgram("remove\n", &first);
15059         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15060         break;
15061
15062       case BeginningOfGame:
15063       default:
15064         break;
15065
15066       case IcsPlayingWhite:
15067       case IcsPlayingBlack:
15068         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15069             SendToICS(ics_prefix);
15070             SendToICS("takeback 2\n");
15071         } else {
15072             SendToICS(ics_prefix);
15073             SendToICS("takeback 1\n");
15074         }
15075         break;
15076     }
15077 }
15078
15079 void
15080 MoveNowEvent ()
15081 {
15082     ChessProgramState *cps;
15083
15084     switch (gameMode) {
15085       case MachinePlaysWhite:
15086         if (!WhiteOnMove(forwardMostMove)) {
15087             DisplayError(_("It is your turn"), 0);
15088             return;
15089         }
15090         cps = &first;
15091         break;
15092       case MachinePlaysBlack:
15093         if (WhiteOnMove(forwardMostMove)) {
15094             DisplayError(_("It is your turn"), 0);
15095             return;
15096         }
15097         cps = &first;
15098         break;
15099       case TwoMachinesPlay:
15100         if (WhiteOnMove(forwardMostMove) ==
15101             (first.twoMachinesColor[0] == 'w')) {
15102             cps = &first;
15103         } else {
15104             cps = &second;
15105         }
15106         break;
15107       case BeginningOfGame:
15108       default:
15109         return;
15110     }
15111     SendToProgram("?\n", cps);
15112 }
15113
15114 void
15115 TruncateGameEvent ()
15116 {
15117     EditGameEvent();
15118     if (gameMode != EditGame) return;
15119     TruncateGame();
15120 }
15121
15122 void
15123 TruncateGame ()
15124 {
15125     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15126     if (forwardMostMove > currentMove) {
15127         if (gameInfo.resultDetails != NULL) {
15128             free(gameInfo.resultDetails);
15129             gameInfo.resultDetails = NULL;
15130             gameInfo.result = GameUnfinished;
15131         }
15132         forwardMostMove = currentMove;
15133         HistorySet(parseList, backwardMostMove, forwardMostMove,
15134                    currentMove-1);
15135     }
15136 }
15137
15138 void
15139 HintEvent ()
15140 {
15141     if (appData.noChessProgram) return;
15142     switch (gameMode) {
15143       case MachinePlaysWhite:
15144         if (WhiteOnMove(forwardMostMove)) {
15145             DisplayError(_("Wait until your turn"), 0);
15146             return;
15147         }
15148         break;
15149       case BeginningOfGame:
15150       case MachinePlaysBlack:
15151         if (!WhiteOnMove(forwardMostMove)) {
15152             DisplayError(_("Wait until your turn"), 0);
15153             return;
15154         }
15155         break;
15156       default:
15157         DisplayError(_("No hint available"), 0);
15158         return;
15159     }
15160     SendToProgram("hint\n", &first);
15161     hintRequested = TRUE;
15162 }
15163
15164 void
15165 CreateBookEvent ()
15166 {
15167     ListGame * lg = (ListGame *) gameList.head;
15168     FILE *f;
15169     int nItem;
15170     static int secondTime = FALSE;
15171
15172     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15173         DisplayError(_("Game list not loaded or empty"), 0);
15174         return;
15175     }
15176
15177     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15178         fclose(f);
15179         secondTime++;
15180         DisplayNote(_("Book file exists! Try again for overwrite."));
15181         return;
15182     }
15183
15184     creatingBook = TRUE;
15185     secondTime = FALSE;
15186
15187     /* Get list size */
15188     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15189         LoadGame(f, nItem, "", TRUE);
15190         AddGameToBook(TRUE);
15191         lg = (ListGame *) lg->node.succ;
15192     }
15193
15194     creatingBook = FALSE;
15195     FlushBook();
15196 }
15197
15198 void
15199 BookEvent ()
15200 {
15201     if (appData.noChessProgram) return;
15202     switch (gameMode) {
15203       case MachinePlaysWhite:
15204         if (WhiteOnMove(forwardMostMove)) {
15205             DisplayError(_("Wait until your turn"), 0);
15206             return;
15207         }
15208         break;
15209       case BeginningOfGame:
15210       case MachinePlaysBlack:
15211         if (!WhiteOnMove(forwardMostMove)) {
15212             DisplayError(_("Wait until your turn"), 0);
15213             return;
15214         }
15215         break;
15216       case EditPosition:
15217         EditPositionDone(TRUE);
15218         break;
15219       case TwoMachinesPlay:
15220         return;
15221       default:
15222         break;
15223     }
15224     SendToProgram("bk\n", &first);
15225     bookOutput[0] = NULLCHAR;
15226     bookRequested = TRUE;
15227 }
15228
15229 void
15230 AboutGameEvent ()
15231 {
15232     char *tags = PGNTags(&gameInfo);
15233     TagsPopUp(tags, CmailMsg());
15234     free(tags);
15235 }
15236
15237 /* end button procedures */
15238
15239 void
15240 PrintPosition (FILE *fp, int move)
15241 {
15242     int i, j;
15243
15244     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15245         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15246             char c = PieceToChar(boards[move][i][j]);
15247             fputc(c == 'x' ? '.' : c, fp);
15248             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15249         }
15250     }
15251     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15252       fprintf(fp, "white to play\n");
15253     else
15254       fprintf(fp, "black to play\n");
15255 }
15256
15257 void
15258 PrintOpponents (FILE *fp)
15259 {
15260     if (gameInfo.white != NULL) {
15261         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15262     } else {
15263         fprintf(fp, "\n");
15264     }
15265 }
15266
15267 /* Find last component of program's own name, using some heuristics */
15268 void
15269 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15270 {
15271     char *p, *q, c;
15272     int local = (strcmp(host, "localhost") == 0);
15273     while (!local && (p = strchr(prog, ';')) != NULL) {
15274         p++;
15275         while (*p == ' ') p++;
15276         prog = p;
15277     }
15278     if (*prog == '"' || *prog == '\'') {
15279         q = strchr(prog + 1, *prog);
15280     } else {
15281         q = strchr(prog, ' ');
15282     }
15283     if (q == NULL) q = prog + strlen(prog);
15284     p = q;
15285     while (p >= prog && *p != '/' && *p != '\\') p--;
15286     p++;
15287     if(p == prog && *p == '"') p++;
15288     c = *q; *q = 0;
15289     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15290     memcpy(buf, p, q - p);
15291     buf[q - p] = NULLCHAR;
15292     if (!local) {
15293         strcat(buf, "@");
15294         strcat(buf, host);
15295     }
15296 }
15297
15298 char *
15299 TimeControlTagValue ()
15300 {
15301     char buf[MSG_SIZ];
15302     if (!appData.clockMode) {
15303       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15304     } else if (movesPerSession > 0) {
15305       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15306     } else if (timeIncrement == 0) {
15307       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15308     } else {
15309       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15310     }
15311     return StrSave(buf);
15312 }
15313
15314 void
15315 SetGameInfo ()
15316 {
15317     /* This routine is used only for certain modes */
15318     VariantClass v = gameInfo.variant;
15319     ChessMove r = GameUnfinished;
15320     char *p = NULL;
15321
15322     if(keepInfo) return;
15323
15324     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15325         r = gameInfo.result;
15326         p = gameInfo.resultDetails;
15327         gameInfo.resultDetails = NULL;
15328     }
15329     ClearGameInfo(&gameInfo);
15330     gameInfo.variant = v;
15331
15332     switch (gameMode) {
15333       case MachinePlaysWhite:
15334         gameInfo.event = StrSave( appData.pgnEventHeader );
15335         gameInfo.site = StrSave(HostName());
15336         gameInfo.date = PGNDate();
15337         gameInfo.round = StrSave("-");
15338         gameInfo.white = StrSave(first.tidy);
15339         gameInfo.black = StrSave(UserName());
15340         gameInfo.timeControl = TimeControlTagValue();
15341         break;
15342
15343       case MachinePlaysBlack:
15344         gameInfo.event = StrSave( appData.pgnEventHeader );
15345         gameInfo.site = StrSave(HostName());
15346         gameInfo.date = PGNDate();
15347         gameInfo.round = StrSave("-");
15348         gameInfo.white = StrSave(UserName());
15349         gameInfo.black = StrSave(first.tidy);
15350         gameInfo.timeControl = TimeControlTagValue();
15351         break;
15352
15353       case TwoMachinesPlay:
15354         gameInfo.event = StrSave( appData.pgnEventHeader );
15355         gameInfo.site = StrSave(HostName());
15356         gameInfo.date = PGNDate();
15357         if (roundNr > 0) {
15358             char buf[MSG_SIZ];
15359             snprintf(buf, MSG_SIZ, "%d", roundNr);
15360             gameInfo.round = StrSave(buf);
15361         } else {
15362             gameInfo.round = StrSave("-");
15363         }
15364         if (first.twoMachinesColor[0] == 'w') {
15365             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15366             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15367         } else {
15368             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15369             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15370         }
15371         gameInfo.timeControl = TimeControlTagValue();
15372         break;
15373
15374       case EditGame:
15375         gameInfo.event = StrSave("Edited game");
15376         gameInfo.site = StrSave(HostName());
15377         gameInfo.date = PGNDate();
15378         gameInfo.round = StrSave("-");
15379         gameInfo.white = StrSave("-");
15380         gameInfo.black = StrSave("-");
15381         gameInfo.result = r;
15382         gameInfo.resultDetails = p;
15383         break;
15384
15385       case EditPosition:
15386         gameInfo.event = StrSave("Edited position");
15387         gameInfo.site = StrSave(HostName());
15388         gameInfo.date = PGNDate();
15389         gameInfo.round = StrSave("-");
15390         gameInfo.white = StrSave("-");
15391         gameInfo.black = StrSave("-");
15392         break;
15393
15394       case IcsPlayingWhite:
15395       case IcsPlayingBlack:
15396       case IcsObserving:
15397       case IcsExamining:
15398         break;
15399
15400       case PlayFromGameFile:
15401         gameInfo.event = StrSave("Game from non-PGN file");
15402         gameInfo.site = StrSave(HostName());
15403         gameInfo.date = PGNDate();
15404         gameInfo.round = StrSave("-");
15405         gameInfo.white = StrSave("?");
15406         gameInfo.black = StrSave("?");
15407         break;
15408
15409       default:
15410         break;
15411     }
15412 }
15413
15414 void
15415 ReplaceComment (int index, char *text)
15416 {
15417     int len;
15418     char *p;
15419     float score;
15420
15421     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15422        pvInfoList[index-1].depth == len &&
15423        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15424        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15425     while (*text == '\n') text++;
15426     len = strlen(text);
15427     while (len > 0 && text[len - 1] == '\n') len--;
15428
15429     if (commentList[index] != NULL)
15430       free(commentList[index]);
15431
15432     if (len == 0) {
15433         commentList[index] = NULL;
15434         return;
15435     }
15436   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15437       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15438       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15439     commentList[index] = (char *) malloc(len + 2);
15440     strncpy(commentList[index], text, len);
15441     commentList[index][len] = '\n';
15442     commentList[index][len + 1] = NULLCHAR;
15443   } else {
15444     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15445     char *p;
15446     commentList[index] = (char *) malloc(len + 7);
15447     safeStrCpy(commentList[index], "{\n", 3);
15448     safeStrCpy(commentList[index]+2, text, len+1);
15449     commentList[index][len+2] = NULLCHAR;
15450     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15451     strcat(commentList[index], "\n}\n");
15452   }
15453 }
15454
15455 void
15456 CrushCRs (char *text)
15457 {
15458   char *p = text;
15459   char *q = text;
15460   char ch;
15461
15462   do {
15463     ch = *p++;
15464     if (ch == '\r') continue;
15465     *q++ = ch;
15466   } while (ch != '\0');
15467 }
15468
15469 void
15470 AppendComment (int index, char *text, Boolean addBraces)
15471 /* addBraces  tells if we should add {} */
15472 {
15473     int oldlen, len;
15474     char *old;
15475
15476 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15477     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15478
15479     CrushCRs(text);
15480     while (*text == '\n') text++;
15481     len = strlen(text);
15482     while (len > 0 && text[len - 1] == '\n') len--;
15483     text[len] = NULLCHAR;
15484
15485     if (len == 0) return;
15486
15487     if (commentList[index] != NULL) {
15488       Boolean addClosingBrace = addBraces;
15489         old = commentList[index];
15490         oldlen = strlen(old);
15491         while(commentList[index][oldlen-1] ==  '\n')
15492           commentList[index][--oldlen] = NULLCHAR;
15493         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15494         safeStrCpy(commentList[index], old, oldlen + len + 6);
15495         free(old);
15496         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15497         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15498           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15499           while (*text == '\n') { text++; len--; }
15500           commentList[index][--oldlen] = NULLCHAR;
15501       }
15502         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15503         else          strcat(commentList[index], "\n");
15504         strcat(commentList[index], text);
15505         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15506         else          strcat(commentList[index], "\n");
15507     } else {
15508         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15509         if(addBraces)
15510           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15511         else commentList[index][0] = NULLCHAR;
15512         strcat(commentList[index], text);
15513         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15514         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15515     }
15516 }
15517
15518 static char *
15519 FindStr (char * text, char * sub_text)
15520 {
15521     char * result = strstr( text, sub_text );
15522
15523     if( result != NULL ) {
15524         result += strlen( sub_text );
15525     }
15526
15527     return result;
15528 }
15529
15530 /* [AS] Try to extract PV info from PGN comment */
15531 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15532 char *
15533 GetInfoFromComment (int index, char * text)
15534 {
15535     char * sep = text, *p;
15536
15537     if( text != NULL && index > 0 ) {
15538         int score = 0;
15539         int depth = 0;
15540         int time = -1, sec = 0, deci;
15541         char * s_eval = FindStr( text, "[%eval " );
15542         char * s_emt = FindStr( text, "[%emt " );
15543
15544         if( s_eval != NULL || s_emt != NULL ) {
15545             /* New style */
15546             char delim;
15547
15548             if( s_eval != NULL ) {
15549                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15550                     return text;
15551                 }
15552
15553                 if( delim != ']' ) {
15554                     return text;
15555                 }
15556             }
15557
15558             if( s_emt != NULL ) {
15559             }
15560                 return text;
15561         }
15562         else {
15563             /* We expect something like: [+|-]nnn.nn/dd */
15564             int score_lo = 0;
15565
15566             if(*text != '{') return text; // [HGM] braces: must be normal comment
15567
15568             sep = strchr( text, '/' );
15569             if( sep == NULL || sep < (text+4) ) {
15570                 return text;
15571             }
15572
15573             p = text;
15574             if(p[1] == '(') { // comment starts with PV
15575                p = strchr(p, ')'); // locate end of PV
15576                if(p == NULL || sep < p+5) return text;
15577                // at this point we have something like "{(.*) +0.23/6 ..."
15578                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15579                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15580                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15581             }
15582             time = -1; sec = -1; deci = -1;
15583             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15584                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15585                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15586                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15587                 return text;
15588             }
15589
15590             if( score_lo < 0 || score_lo >= 100 ) {
15591                 return text;
15592             }
15593
15594             if(sec >= 0) time = 600*time + 10*sec; else
15595             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15596
15597             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15598
15599             /* [HGM] PV time: now locate end of PV info */
15600             while( *++sep >= '0' && *sep <= '9'); // strip depth
15601             if(time >= 0)
15602             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15603             if(sec >= 0)
15604             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15605             if(deci >= 0)
15606             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15607             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15608         }
15609
15610         if( depth <= 0 ) {
15611             return text;
15612         }
15613
15614         if( time < 0 ) {
15615             time = -1;
15616         }
15617
15618         pvInfoList[index-1].depth = depth;
15619         pvInfoList[index-1].score = score;
15620         pvInfoList[index-1].time  = 10*time; // centi-sec
15621         if(*sep == '}') *sep = 0; else *--sep = '{';
15622         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15623     }
15624     return sep;
15625 }
15626
15627 void
15628 SendToProgram (char *message, ChessProgramState *cps)
15629 {
15630     int count, outCount, error;
15631     char buf[MSG_SIZ];
15632
15633     if (cps->pr == NoProc) return;
15634     Attention(cps);
15635
15636     if (appData.debugMode) {
15637         TimeMark now;
15638         GetTimeMark(&now);
15639         fprintf(debugFP, "%ld >%-6s: %s",
15640                 SubtractTimeMarks(&now, &programStartTime),
15641                 cps->which, message);
15642         if(serverFP)
15643             fprintf(serverFP, "%ld >%-6s: %s",
15644                 SubtractTimeMarks(&now, &programStartTime),
15645                 cps->which, message), fflush(serverFP);
15646     }
15647
15648     count = strlen(message);
15649     outCount = OutputToProcess(cps->pr, message, count, &error);
15650     if (outCount < count && !exiting
15651                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15652       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15653       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15654         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15655             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15656                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15657                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15658                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15659             } else {
15660                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15661                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15662                 gameInfo.result = res;
15663             }
15664             gameInfo.resultDetails = StrSave(buf);
15665         }
15666         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15667         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15668     }
15669 }
15670
15671 void
15672 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15673 {
15674     char *end_str;
15675     char buf[MSG_SIZ];
15676     ChessProgramState *cps = (ChessProgramState *)closure;
15677
15678     if (isr != cps->isr) return; /* Killed intentionally */
15679     if (count <= 0) {
15680         if (count == 0) {
15681             RemoveInputSource(cps->isr);
15682             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15683                     _(cps->which), cps->program);
15684             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15685             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15686                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15687                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15688                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15689                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15690                 } else {
15691                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15692                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15693                     gameInfo.result = res;
15694                 }
15695                 gameInfo.resultDetails = StrSave(buf);
15696             }
15697             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15698             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15699         } else {
15700             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15701                     _(cps->which), cps->program);
15702             RemoveInputSource(cps->isr);
15703
15704             /* [AS] Program is misbehaving badly... kill it */
15705             if( count == -2 ) {
15706                 DestroyChildProcess( cps->pr, 9 );
15707                 cps->pr = NoProc;
15708             }
15709
15710             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15711         }
15712         return;
15713     }
15714
15715     if ((end_str = strchr(message, '\r')) != NULL)
15716       *end_str = NULLCHAR;
15717     if ((end_str = strchr(message, '\n')) != NULL)
15718       *end_str = NULLCHAR;
15719
15720     if (appData.debugMode) {
15721         TimeMark now; int print = 1;
15722         char *quote = ""; char c; int i;
15723
15724         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15725                 char start = message[0];
15726                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15727                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15728                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15729                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15730                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15731                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15732                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15733                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15734                    sscanf(message, "hint: %c", &c)!=1 &&
15735                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15736                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15737                     print = (appData.engineComments >= 2);
15738                 }
15739                 message[0] = start; // restore original message
15740         }
15741         if(print) {
15742                 GetTimeMark(&now);
15743                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15744                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15745                         quote,
15746                         message);
15747                 if(serverFP)
15748                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15749                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15750                         quote,
15751                         message), fflush(serverFP);
15752         }
15753     }
15754
15755     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15756     if (appData.icsEngineAnalyze) {
15757         if (strstr(message, "whisper") != NULL ||
15758              strstr(message, "kibitz") != NULL ||
15759             strstr(message, "tellics") != NULL) return;
15760     }
15761
15762     HandleMachineMove(message, cps);
15763 }
15764
15765
15766 void
15767 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15768 {
15769     char buf[MSG_SIZ];
15770     int seconds;
15771
15772     if( timeControl_2 > 0 ) {
15773         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15774             tc = timeControl_2;
15775         }
15776     }
15777     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15778     inc /= cps->timeOdds;
15779     st  /= cps->timeOdds;
15780
15781     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15782
15783     if (st > 0) {
15784       /* Set exact time per move, normally using st command */
15785       if (cps->stKludge) {
15786         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15787         seconds = st % 60;
15788         if (seconds == 0) {
15789           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15790         } else {
15791           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15792         }
15793       } else {
15794         snprintf(buf, MSG_SIZ, "st %d\n", st);
15795       }
15796     } else {
15797       /* Set conventional or incremental time control, using level command */
15798       if (seconds == 0) {
15799         /* Note old gnuchess bug -- minutes:seconds used to not work.
15800            Fixed in later versions, but still avoid :seconds
15801            when seconds is 0. */
15802         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15803       } else {
15804         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15805                  seconds, inc/1000.);
15806       }
15807     }
15808     SendToProgram(buf, cps);
15809
15810     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15811     /* Orthogonally, limit search to given depth */
15812     if (sd > 0) {
15813       if (cps->sdKludge) {
15814         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15815       } else {
15816         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15817       }
15818       SendToProgram(buf, cps);
15819     }
15820
15821     if(cps->nps >= 0) { /* [HGM] nps */
15822         if(cps->supportsNPS == FALSE)
15823           cps->nps = -1; // don't use if engine explicitly says not supported!
15824         else {
15825           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15826           SendToProgram(buf, cps);
15827         }
15828     }
15829 }
15830
15831 ChessProgramState *
15832 WhitePlayer ()
15833 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15834 {
15835     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15836        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15837         return &second;
15838     return &first;
15839 }
15840
15841 void
15842 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15843 {
15844     char message[MSG_SIZ];
15845     long time, otime;
15846
15847     /* Note: this routine must be called when the clocks are stopped
15848        or when they have *just* been set or switched; otherwise
15849        it will be off by the time since the current tick started.
15850     */
15851     if (machineWhite) {
15852         time = whiteTimeRemaining / 10;
15853         otime = blackTimeRemaining / 10;
15854     } else {
15855         time = blackTimeRemaining / 10;
15856         otime = whiteTimeRemaining / 10;
15857     }
15858     /* [HGM] translate opponent's time by time-odds factor */
15859     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15860
15861     if (time <= 0) time = 1;
15862     if (otime <= 0) otime = 1;
15863
15864     snprintf(message, MSG_SIZ, "time %ld\n", time);
15865     SendToProgram(message, cps);
15866
15867     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15868     SendToProgram(message, cps);
15869 }
15870
15871 int
15872 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15873 {
15874   char buf[MSG_SIZ];
15875   int len = strlen(name);
15876   int val;
15877
15878   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15879     (*p) += len + 1;
15880     sscanf(*p, "%d", &val);
15881     *loc = (val != 0);
15882     while (**p && **p != ' ')
15883       (*p)++;
15884     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15885     SendToProgram(buf, cps);
15886     return TRUE;
15887   }
15888   return FALSE;
15889 }
15890
15891 int
15892 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15893 {
15894   char buf[MSG_SIZ];
15895   int len = strlen(name);
15896   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15897     (*p) += len + 1;
15898     sscanf(*p, "%d", loc);
15899     while (**p && **p != ' ') (*p)++;
15900     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15901     SendToProgram(buf, cps);
15902     return TRUE;
15903   }
15904   return FALSE;
15905 }
15906
15907 int
15908 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15909 {
15910   char buf[MSG_SIZ];
15911   int len = strlen(name);
15912   if (strncmp((*p), name, len) == 0
15913       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15914     (*p) += len + 2;
15915     sscanf(*p, "%[^\"]", loc);
15916     while (**p && **p != '\"') (*p)++;
15917     if (**p == '\"') (*p)++;
15918     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15919     SendToProgram(buf, cps);
15920     return TRUE;
15921   }
15922   return FALSE;
15923 }
15924
15925 int
15926 ParseOption (Option *opt, ChessProgramState *cps)
15927 // [HGM] options: process the string that defines an engine option, and determine
15928 // name, type, default value, and allowed value range
15929 {
15930         char *p, *q, buf[MSG_SIZ];
15931         int n, min = (-1)<<31, max = 1<<31, def;
15932
15933         if(p = strstr(opt->name, " -spin ")) {
15934             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15935             if(max < min) max = min; // enforce consistency
15936             if(def < min) def = min;
15937             if(def > max) def = max;
15938             opt->value = def;
15939             opt->min = min;
15940             opt->max = max;
15941             opt->type = Spin;
15942         } else if((p = strstr(opt->name, " -slider "))) {
15943             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15944             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15945             if(max < min) max = min; // enforce consistency
15946             if(def < min) def = min;
15947             if(def > max) def = max;
15948             opt->value = def;
15949             opt->min = min;
15950             opt->max = max;
15951             opt->type = Spin; // Slider;
15952         } else if((p = strstr(opt->name, " -string "))) {
15953             opt->textValue = p+9;
15954             opt->type = TextBox;
15955         } else if((p = strstr(opt->name, " -file "))) {
15956             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15957             opt->textValue = p+7;
15958             opt->type = FileName; // FileName;
15959         } else if((p = strstr(opt->name, " -path "))) {
15960             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15961             opt->textValue = p+7;
15962             opt->type = PathName; // PathName;
15963         } else if(p = strstr(opt->name, " -check ")) {
15964             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15965             opt->value = (def != 0);
15966             opt->type = CheckBox;
15967         } else if(p = strstr(opt->name, " -combo ")) {
15968             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15969             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15970             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15971             opt->value = n = 0;
15972             while(q = StrStr(q, " /// ")) {
15973                 n++; *q = 0;    // count choices, and null-terminate each of them
15974                 q += 5;
15975                 if(*q == '*') { // remember default, which is marked with * prefix
15976                     q++;
15977                     opt->value = n;
15978                 }
15979                 cps->comboList[cps->comboCnt++] = q;
15980             }
15981             cps->comboList[cps->comboCnt++] = NULL;
15982             opt->max = n + 1;
15983             opt->type = ComboBox;
15984         } else if(p = strstr(opt->name, " -button")) {
15985             opt->type = Button;
15986         } else if(p = strstr(opt->name, " -save")) {
15987             opt->type = SaveButton;
15988         } else return FALSE;
15989         *p = 0; // terminate option name
15990         // now look if the command-line options define a setting for this engine option.
15991         if(cps->optionSettings && cps->optionSettings[0])
15992             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15993         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15994           snprintf(buf, MSG_SIZ, "option %s", p);
15995                 if(p = strstr(buf, ",")) *p = 0;
15996                 if(q = strchr(buf, '=')) switch(opt->type) {
15997                     case ComboBox:
15998                         for(n=0; n<opt->max; n++)
15999                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16000                         break;
16001                     case TextBox:
16002                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16003                         break;
16004                     case Spin:
16005                     case CheckBox:
16006                         opt->value = atoi(q+1);
16007                     default:
16008                         break;
16009                 }
16010                 strcat(buf, "\n");
16011                 SendToProgram(buf, cps);
16012         }
16013         return TRUE;
16014 }
16015
16016 void
16017 FeatureDone (ChessProgramState *cps, int val)
16018 {
16019   DelayedEventCallback cb = GetDelayedEvent();
16020   if ((cb == InitBackEnd3 && cps == &first) ||
16021       (cb == SettingsMenuIfReady && cps == &second) ||
16022       (cb == LoadEngine) ||
16023       (cb == TwoMachinesEventIfReady)) {
16024     CancelDelayedEvent();
16025     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16026   }
16027   cps->initDone = val;
16028   if(val) cps->reload = FALSE;
16029 }
16030
16031 /* Parse feature command from engine */
16032 void
16033 ParseFeatures (char *args, ChessProgramState *cps)
16034 {
16035   char *p = args;
16036   char *q;
16037   int val;
16038   char buf[MSG_SIZ];
16039
16040   for (;;) {
16041     while (*p == ' ') p++;
16042     if (*p == NULLCHAR) return;
16043
16044     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16045     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16046     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16047     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16048     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16049     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16050     if (BoolFeature(&p, "reuse", &val, cps)) {
16051       /* Engine can disable reuse, but can't enable it if user said no */
16052       if (!val) cps->reuse = FALSE;
16053       continue;
16054     }
16055     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16056     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16057       if (gameMode == TwoMachinesPlay) {
16058         DisplayTwoMachinesTitle();
16059       } else {
16060         DisplayTitle("");
16061       }
16062       continue;
16063     }
16064     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16065     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16066     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16067     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16068     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16069     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16070     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16071     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16072     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16073     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16074     if (IntFeature(&p, "done", &val, cps)) {
16075       FeatureDone(cps, val);
16076       continue;
16077     }
16078     /* Added by Tord: */
16079     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16080     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16081     /* End of additions by Tord */
16082
16083     /* [HGM] added features: */
16084     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16085     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16086     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16087     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16088     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16089     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16090     if (StringFeature(&p, "option", buf, cps)) {
16091         if(cps->reload) continue; // we are reloading because of xreuse
16092         FREE(cps->option[cps->nrOptions].name);
16093         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16094         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16095         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16096           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16097             SendToProgram(buf, cps);
16098             continue;
16099         }
16100         if(cps->nrOptions >= MAX_OPTIONS) {
16101             cps->nrOptions--;
16102             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16103             DisplayError(buf, 0);
16104         }
16105         continue;
16106     }
16107     /* End of additions by HGM */
16108
16109     /* unknown feature: complain and skip */
16110     q = p;
16111     while (*q && *q != '=') q++;
16112     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16113     SendToProgram(buf, cps);
16114     p = q;
16115     if (*p == '=') {
16116       p++;
16117       if (*p == '\"') {
16118         p++;
16119         while (*p && *p != '\"') p++;
16120         if (*p == '\"') p++;
16121       } else {
16122         while (*p && *p != ' ') p++;
16123       }
16124     }
16125   }
16126
16127 }
16128
16129 void
16130 PeriodicUpdatesEvent (int newState)
16131 {
16132     if (newState == appData.periodicUpdates)
16133       return;
16134
16135     appData.periodicUpdates=newState;
16136
16137     /* Display type changes, so update it now */
16138 //    DisplayAnalysis();
16139
16140     /* Get the ball rolling again... */
16141     if (newState) {
16142         AnalysisPeriodicEvent(1);
16143         StartAnalysisClock();
16144     }
16145 }
16146
16147 void
16148 PonderNextMoveEvent (int newState)
16149 {
16150     if (newState == appData.ponderNextMove) return;
16151     if (gameMode == EditPosition) EditPositionDone(TRUE);
16152     if (newState) {
16153         SendToProgram("hard\n", &first);
16154         if (gameMode == TwoMachinesPlay) {
16155             SendToProgram("hard\n", &second);
16156         }
16157     } else {
16158         SendToProgram("easy\n", &first);
16159         thinkOutput[0] = NULLCHAR;
16160         if (gameMode == TwoMachinesPlay) {
16161             SendToProgram("easy\n", &second);
16162         }
16163     }
16164     appData.ponderNextMove = newState;
16165 }
16166
16167 void
16168 NewSettingEvent (int option, int *feature, char *command, int value)
16169 {
16170     char buf[MSG_SIZ];
16171
16172     if (gameMode == EditPosition) EditPositionDone(TRUE);
16173     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16174     if(feature == NULL || *feature) SendToProgram(buf, &first);
16175     if (gameMode == TwoMachinesPlay) {
16176         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16177     }
16178 }
16179
16180 void
16181 ShowThinkingEvent ()
16182 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16183 {
16184     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16185     int newState = appData.showThinking
16186         // [HGM] thinking: other features now need thinking output as well
16187         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16188
16189     if (oldState == newState) return;
16190     oldState = newState;
16191     if (gameMode == EditPosition) EditPositionDone(TRUE);
16192     if (oldState) {
16193         SendToProgram("post\n", &first);
16194         if (gameMode == TwoMachinesPlay) {
16195             SendToProgram("post\n", &second);
16196         }
16197     } else {
16198         SendToProgram("nopost\n", &first);
16199         thinkOutput[0] = NULLCHAR;
16200         if (gameMode == TwoMachinesPlay) {
16201             SendToProgram("nopost\n", &second);
16202         }
16203     }
16204 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16205 }
16206
16207 void
16208 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16209 {
16210   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16211   if (pr == NoProc) return;
16212   AskQuestion(title, question, replyPrefix, pr);
16213 }
16214
16215 void
16216 TypeInEvent (char firstChar)
16217 {
16218     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16219         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16220         gameMode == AnalyzeMode || gameMode == EditGame ||
16221         gameMode == EditPosition || gameMode == IcsExamining ||
16222         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16223         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16224                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16225                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16226         gameMode == Training) PopUpMoveDialog(firstChar);
16227 }
16228
16229 void
16230 TypeInDoneEvent (char *move)
16231 {
16232         Board board;
16233         int n, fromX, fromY, toX, toY;
16234         char promoChar;
16235         ChessMove moveType;
16236
16237         // [HGM] FENedit
16238         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16239                 EditPositionPasteFEN(move);
16240                 return;
16241         }
16242         // [HGM] movenum: allow move number to be typed in any mode
16243         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16244           ToNrEvent(2*n-1);
16245           return;
16246         }
16247         // undocumented kludge: allow command-line option to be typed in!
16248         // (potentially fatal, and does not implement the effect of the option.)
16249         // should only be used for options that are values on which future decisions will be made,
16250         // and definitely not on options that would be used during initialization.
16251         if(strstr(move, "!!! -") == move) {
16252             ParseArgsFromString(move+4);
16253             return;
16254         }
16255
16256       if (gameMode != EditGame && currentMove != forwardMostMove &&
16257         gameMode != Training) {
16258         DisplayMoveError(_("Displayed move is not current"));
16259       } else {
16260         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16261           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16262         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16263         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16264           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16265           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16266         } else {
16267           DisplayMoveError(_("Could not parse move"));
16268         }
16269       }
16270 }
16271
16272 void
16273 DisplayMove (int moveNumber)
16274 {
16275     char message[MSG_SIZ];
16276     char res[MSG_SIZ];
16277     char cpThinkOutput[MSG_SIZ];
16278
16279     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16280
16281     if (moveNumber == forwardMostMove - 1 ||
16282         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16283
16284         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16285
16286         if (strchr(cpThinkOutput, '\n')) {
16287             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16288         }
16289     } else {
16290         *cpThinkOutput = NULLCHAR;
16291     }
16292
16293     /* [AS] Hide thinking from human user */
16294     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16295         *cpThinkOutput = NULLCHAR;
16296         if( thinkOutput[0] != NULLCHAR ) {
16297             int i;
16298
16299             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16300                 cpThinkOutput[i] = '.';
16301             }
16302             cpThinkOutput[i] = NULLCHAR;
16303             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16304         }
16305     }
16306
16307     if (moveNumber == forwardMostMove - 1 &&
16308         gameInfo.resultDetails != NULL) {
16309         if (gameInfo.resultDetails[0] == NULLCHAR) {
16310           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16311         } else {
16312           snprintf(res, MSG_SIZ, " {%s} %s",
16313                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16314         }
16315     } else {
16316         res[0] = NULLCHAR;
16317     }
16318
16319     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16320         DisplayMessage(res, cpThinkOutput);
16321     } else {
16322       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16323                 WhiteOnMove(moveNumber) ? " " : ".. ",
16324                 parseList[moveNumber], res);
16325         DisplayMessage(message, cpThinkOutput);
16326     }
16327 }
16328
16329 void
16330 DisplayComment (int moveNumber, char *text)
16331 {
16332     char title[MSG_SIZ];
16333
16334     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16335       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16336     } else {
16337       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16338               WhiteOnMove(moveNumber) ? " " : ".. ",
16339               parseList[moveNumber]);
16340     }
16341     if (text != NULL && (appData.autoDisplayComment || commentUp))
16342         CommentPopUp(title, text);
16343 }
16344
16345 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16346  * might be busy thinking or pondering.  It can be omitted if your
16347  * gnuchess is configured to stop thinking immediately on any user
16348  * input.  However, that gnuchess feature depends on the FIONREAD
16349  * ioctl, which does not work properly on some flavors of Unix.
16350  */
16351 void
16352 Attention (ChessProgramState *cps)
16353 {
16354 #if ATTENTION
16355     if (!cps->useSigint) return;
16356     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16357     switch (gameMode) {
16358       case MachinePlaysWhite:
16359       case MachinePlaysBlack:
16360       case TwoMachinesPlay:
16361       case IcsPlayingWhite:
16362       case IcsPlayingBlack:
16363       case AnalyzeMode:
16364       case AnalyzeFile:
16365         /* Skip if we know it isn't thinking */
16366         if (!cps->maybeThinking) return;
16367         if (appData.debugMode)
16368           fprintf(debugFP, "Interrupting %s\n", cps->which);
16369         InterruptChildProcess(cps->pr);
16370         cps->maybeThinking = FALSE;
16371         break;
16372       default:
16373         break;
16374     }
16375 #endif /*ATTENTION*/
16376 }
16377
16378 int
16379 CheckFlags ()
16380 {
16381     if (whiteTimeRemaining <= 0) {
16382         if (!whiteFlag) {
16383             whiteFlag = TRUE;
16384             if (appData.icsActive) {
16385                 if (appData.autoCallFlag &&
16386                     gameMode == IcsPlayingBlack && !blackFlag) {
16387                   SendToICS(ics_prefix);
16388                   SendToICS("flag\n");
16389                 }
16390             } else {
16391                 if (blackFlag) {
16392                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16393                 } else {
16394                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16395                     if (appData.autoCallFlag) {
16396                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16397                         return TRUE;
16398                     }
16399                 }
16400             }
16401         }
16402     }
16403     if (blackTimeRemaining <= 0) {
16404         if (!blackFlag) {
16405             blackFlag = TRUE;
16406             if (appData.icsActive) {
16407                 if (appData.autoCallFlag &&
16408                     gameMode == IcsPlayingWhite && !whiteFlag) {
16409                   SendToICS(ics_prefix);
16410                   SendToICS("flag\n");
16411                 }
16412             } else {
16413                 if (whiteFlag) {
16414                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16415                 } else {
16416                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16417                     if (appData.autoCallFlag) {
16418                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16419                         return TRUE;
16420                     }
16421                 }
16422             }
16423         }
16424     }
16425     return FALSE;
16426 }
16427
16428 void
16429 CheckTimeControl ()
16430 {
16431     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16432         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16433
16434     /*
16435      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16436      */
16437     if ( !WhiteOnMove(forwardMostMove) ) {
16438         /* White made time control */
16439         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16440         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16441         /* [HGM] time odds: correct new time quota for time odds! */
16442                                             / WhitePlayer()->timeOdds;
16443         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16444     } else {
16445         lastBlack -= blackTimeRemaining;
16446         /* Black made time control */
16447         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16448                                             / WhitePlayer()->other->timeOdds;
16449         lastWhite = whiteTimeRemaining;
16450     }
16451 }
16452
16453 void
16454 DisplayBothClocks ()
16455 {
16456     int wom = gameMode == EditPosition ?
16457       !blackPlaysFirst : WhiteOnMove(currentMove);
16458     DisplayWhiteClock(whiteTimeRemaining, wom);
16459     DisplayBlackClock(blackTimeRemaining, !wom);
16460 }
16461
16462
16463 /* Timekeeping seems to be a portability nightmare.  I think everyone
16464    has ftime(), but I'm really not sure, so I'm including some ifdefs
16465    to use other calls if you don't.  Clocks will be less accurate if
16466    you have neither ftime nor gettimeofday.
16467 */
16468
16469 /* VS 2008 requires the #include outside of the function */
16470 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16471 #include <sys/timeb.h>
16472 #endif
16473
16474 /* Get the current time as a TimeMark */
16475 void
16476 GetTimeMark (TimeMark *tm)
16477 {
16478 #if HAVE_GETTIMEOFDAY
16479
16480     struct timeval timeVal;
16481     struct timezone timeZone;
16482
16483     gettimeofday(&timeVal, &timeZone);
16484     tm->sec = (long) timeVal.tv_sec;
16485     tm->ms = (int) (timeVal.tv_usec / 1000L);
16486
16487 #else /*!HAVE_GETTIMEOFDAY*/
16488 #if HAVE_FTIME
16489
16490 // include <sys/timeb.h> / moved to just above start of function
16491     struct timeb timeB;
16492
16493     ftime(&timeB);
16494     tm->sec = (long) timeB.time;
16495     tm->ms = (int) timeB.millitm;
16496
16497 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16498     tm->sec = (long) time(NULL);
16499     tm->ms = 0;
16500 #endif
16501 #endif
16502 }
16503
16504 /* Return the difference in milliseconds between two
16505    time marks.  We assume the difference will fit in a long!
16506 */
16507 long
16508 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16509 {
16510     return 1000L*(tm2->sec - tm1->sec) +
16511            (long) (tm2->ms - tm1->ms);
16512 }
16513
16514
16515 /*
16516  * Code to manage the game clocks.
16517  *
16518  * In tournament play, black starts the clock and then white makes a move.
16519  * We give the human user a slight advantage if he is playing white---the
16520  * clocks don't run until he makes his first move, so it takes zero time.
16521  * Also, we don't account for network lag, so we could get out of sync
16522  * with GNU Chess's clock -- but then, referees are always right.
16523  */
16524
16525 static TimeMark tickStartTM;
16526 static long intendedTickLength;
16527
16528 long
16529 NextTickLength (long timeRemaining)
16530 {
16531     long nominalTickLength, nextTickLength;
16532
16533     if (timeRemaining > 0L && timeRemaining <= 10000L)
16534       nominalTickLength = 100L;
16535     else
16536       nominalTickLength = 1000L;
16537     nextTickLength = timeRemaining % nominalTickLength;
16538     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16539
16540     return nextTickLength;
16541 }
16542
16543 /* Adjust clock one minute up or down */
16544 void
16545 AdjustClock (Boolean which, int dir)
16546 {
16547     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16548     if(which) blackTimeRemaining += 60000*dir;
16549     else      whiteTimeRemaining += 60000*dir;
16550     DisplayBothClocks();
16551     adjustedClock = TRUE;
16552 }
16553
16554 /* Stop clocks and reset to a fresh time control */
16555 void
16556 ResetClocks ()
16557 {
16558     (void) StopClockTimer();
16559     if (appData.icsActive) {
16560         whiteTimeRemaining = blackTimeRemaining = 0;
16561     } else if (searchTime) {
16562         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16563         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16564     } else { /* [HGM] correct new time quote for time odds */
16565         whiteTC = blackTC = fullTimeControlString;
16566         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16567         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16568     }
16569     if (whiteFlag || blackFlag) {
16570         DisplayTitle("");
16571         whiteFlag = blackFlag = FALSE;
16572     }
16573     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16574     DisplayBothClocks();
16575     adjustedClock = FALSE;
16576 }
16577
16578 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16579
16580 /* Decrement running clock by amount of time that has passed */
16581 void
16582 DecrementClocks ()
16583 {
16584     long timeRemaining;
16585     long lastTickLength, fudge;
16586     TimeMark now;
16587
16588     if (!appData.clockMode) return;
16589     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16590
16591     GetTimeMark(&now);
16592
16593     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16594
16595     /* Fudge if we woke up a little too soon */
16596     fudge = intendedTickLength - lastTickLength;
16597     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16598
16599     if (WhiteOnMove(forwardMostMove)) {
16600         if(whiteNPS >= 0) lastTickLength = 0;
16601         timeRemaining = whiteTimeRemaining -= lastTickLength;
16602         if(timeRemaining < 0 && !appData.icsActive) {
16603             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16604             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16605                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16606                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16607             }
16608         }
16609         DisplayWhiteClock(whiteTimeRemaining - fudge,
16610                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16611     } else {
16612         if(blackNPS >= 0) lastTickLength = 0;
16613         timeRemaining = blackTimeRemaining -= lastTickLength;
16614         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16615             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16616             if(suddenDeath) {
16617                 blackStartMove = forwardMostMove;
16618                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16619             }
16620         }
16621         DisplayBlackClock(blackTimeRemaining - fudge,
16622                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16623     }
16624     if (CheckFlags()) return;
16625
16626     if(twoBoards) { // count down secondary board's clocks as well
16627         activePartnerTime -= lastTickLength;
16628         partnerUp = 1;
16629         if(activePartner == 'W')
16630             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16631         else
16632             DisplayBlackClock(activePartnerTime, TRUE);
16633         partnerUp = 0;
16634     }
16635
16636     tickStartTM = now;
16637     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16638     StartClockTimer(intendedTickLength);
16639
16640     /* if the time remaining has fallen below the alarm threshold, sound the
16641      * alarm. if the alarm has sounded and (due to a takeback or time control
16642      * with increment) the time remaining has increased to a level above the
16643      * threshold, reset the alarm so it can sound again.
16644      */
16645
16646     if (appData.icsActive && appData.icsAlarm) {
16647
16648         /* make sure we are dealing with the user's clock */
16649         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16650                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16651            )) return;
16652
16653         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16654             alarmSounded = FALSE;
16655         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16656             PlayAlarmSound();
16657             alarmSounded = TRUE;
16658         }
16659     }
16660 }
16661
16662
16663 /* A player has just moved, so stop the previously running
16664    clock and (if in clock mode) start the other one.
16665    We redisplay both clocks in case we're in ICS mode, because
16666    ICS gives us an update to both clocks after every move.
16667    Note that this routine is called *after* forwardMostMove
16668    is updated, so the last fractional tick must be subtracted
16669    from the color that is *not* on move now.
16670 */
16671 void
16672 SwitchClocks (int newMoveNr)
16673 {
16674     long lastTickLength;
16675     TimeMark now;
16676     int flagged = FALSE;
16677
16678     GetTimeMark(&now);
16679
16680     if (StopClockTimer() && appData.clockMode) {
16681         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16682         if (!WhiteOnMove(forwardMostMove)) {
16683             if(blackNPS >= 0) lastTickLength = 0;
16684             blackTimeRemaining -= lastTickLength;
16685            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16686 //         if(pvInfoList[forwardMostMove].time == -1)
16687                  pvInfoList[forwardMostMove].time =               // use GUI time
16688                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16689         } else {
16690            if(whiteNPS >= 0) lastTickLength = 0;
16691            whiteTimeRemaining -= lastTickLength;
16692            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16693 //         if(pvInfoList[forwardMostMove].time == -1)
16694                  pvInfoList[forwardMostMove].time =
16695                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16696         }
16697         flagged = CheckFlags();
16698     }
16699     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16700     CheckTimeControl();
16701
16702     if (flagged || !appData.clockMode) return;
16703
16704     switch (gameMode) {
16705       case MachinePlaysBlack:
16706       case MachinePlaysWhite:
16707       case BeginningOfGame:
16708         if (pausing) return;
16709         break;
16710
16711       case EditGame:
16712       case PlayFromGameFile:
16713       case IcsExamining:
16714         return;
16715
16716       default:
16717         break;
16718     }
16719
16720     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16721         if(WhiteOnMove(forwardMostMove))
16722              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16723         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16724     }
16725
16726     tickStartTM = now;
16727     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16728       whiteTimeRemaining : blackTimeRemaining);
16729     StartClockTimer(intendedTickLength);
16730 }
16731
16732
16733 /* Stop both clocks */
16734 void
16735 StopClocks ()
16736 {
16737     long lastTickLength;
16738     TimeMark now;
16739
16740     if (!StopClockTimer()) return;
16741     if (!appData.clockMode) return;
16742
16743     GetTimeMark(&now);
16744
16745     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16746     if (WhiteOnMove(forwardMostMove)) {
16747         if(whiteNPS >= 0) lastTickLength = 0;
16748         whiteTimeRemaining -= lastTickLength;
16749         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16750     } else {
16751         if(blackNPS >= 0) lastTickLength = 0;
16752         blackTimeRemaining -= lastTickLength;
16753         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16754     }
16755     CheckFlags();
16756 }
16757
16758 /* Start clock of player on move.  Time may have been reset, so
16759    if clock is already running, stop and restart it. */
16760 void
16761 StartClocks ()
16762 {
16763     (void) StopClockTimer(); /* in case it was running already */
16764     DisplayBothClocks();
16765     if (CheckFlags()) return;
16766
16767     if (!appData.clockMode) return;
16768     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16769
16770     GetTimeMark(&tickStartTM);
16771     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16772       whiteTimeRemaining : blackTimeRemaining);
16773
16774    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16775     whiteNPS = blackNPS = -1;
16776     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16777        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16778         whiteNPS = first.nps;
16779     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16780        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16781         blackNPS = first.nps;
16782     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16783         whiteNPS = second.nps;
16784     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16785         blackNPS = second.nps;
16786     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16787
16788     StartClockTimer(intendedTickLength);
16789 }
16790
16791 char *
16792 TimeString (long ms)
16793 {
16794     long second, minute, hour, day;
16795     char *sign = "";
16796     static char buf[32];
16797
16798     if (ms > 0 && ms <= 9900) {
16799       /* convert milliseconds to tenths, rounding up */
16800       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16801
16802       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16803       return buf;
16804     }
16805
16806     /* convert milliseconds to seconds, rounding up */
16807     /* use floating point to avoid strangeness of integer division
16808        with negative dividends on many machines */
16809     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16810
16811     if (second < 0) {
16812         sign = "-";
16813         second = -second;
16814     }
16815
16816     day = second / (60 * 60 * 24);
16817     second = second % (60 * 60 * 24);
16818     hour = second / (60 * 60);
16819     second = second % (60 * 60);
16820     minute = second / 60;
16821     second = second % 60;
16822
16823     if (day > 0)
16824       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16825               sign, day, hour, minute, second);
16826     else if (hour > 0)
16827       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16828     else
16829       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16830
16831     return buf;
16832 }
16833
16834
16835 /*
16836  * This is necessary because some C libraries aren't ANSI C compliant yet.
16837  */
16838 char *
16839 StrStr (char *string, char *match)
16840 {
16841     int i, length;
16842
16843     length = strlen(match);
16844
16845     for (i = strlen(string) - length; i >= 0; i--, string++)
16846       if (!strncmp(match, string, length))
16847         return string;
16848
16849     return NULL;
16850 }
16851
16852 char *
16853 StrCaseStr (char *string, char *match)
16854 {
16855     int i, j, length;
16856
16857     length = strlen(match);
16858
16859     for (i = strlen(string) - length; i >= 0; i--, string++) {
16860         for (j = 0; j < length; j++) {
16861             if (ToLower(match[j]) != ToLower(string[j]))
16862               break;
16863         }
16864         if (j == length) return string;
16865     }
16866
16867     return NULL;
16868 }
16869
16870 #ifndef _amigados
16871 int
16872 StrCaseCmp (char *s1, char *s2)
16873 {
16874     char c1, c2;
16875
16876     for (;;) {
16877         c1 = ToLower(*s1++);
16878         c2 = ToLower(*s2++);
16879         if (c1 > c2) return 1;
16880         if (c1 < c2) return -1;
16881         if (c1 == NULLCHAR) return 0;
16882     }
16883 }
16884
16885
16886 int
16887 ToLower (int c)
16888 {
16889     return isupper(c) ? tolower(c) : c;
16890 }
16891
16892
16893 int
16894 ToUpper (int c)
16895 {
16896     return islower(c) ? toupper(c) : c;
16897 }
16898 #endif /* !_amigados    */
16899
16900 char *
16901 StrSave (char *s)
16902 {
16903   char *ret;
16904
16905   if ((ret = (char *) malloc(strlen(s) + 1)))
16906     {
16907       safeStrCpy(ret, s, strlen(s)+1);
16908     }
16909   return ret;
16910 }
16911
16912 char *
16913 StrSavePtr (char *s, char **savePtr)
16914 {
16915     if (*savePtr) {
16916         free(*savePtr);
16917     }
16918     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16919       safeStrCpy(*savePtr, s, strlen(s)+1);
16920     }
16921     return(*savePtr);
16922 }
16923
16924 char *
16925 PGNDate ()
16926 {
16927     time_t clock;
16928     struct tm *tm;
16929     char buf[MSG_SIZ];
16930
16931     clock = time((time_t *)NULL);
16932     tm = localtime(&clock);
16933     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16934             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16935     return StrSave(buf);
16936 }
16937
16938
16939 char *
16940 PositionToFEN (int move, char *overrideCastling)
16941 {
16942     int i, j, fromX, fromY, toX, toY;
16943     int whiteToPlay;
16944     char buf[MSG_SIZ];
16945     char *p, *q;
16946     int emptycount;
16947     ChessSquare piece;
16948
16949     whiteToPlay = (gameMode == EditPosition) ?
16950       !blackPlaysFirst : (move % 2 == 0);
16951     p = buf;
16952
16953     /* Piece placement data */
16954     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16955         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16956         emptycount = 0;
16957         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16958             if (boards[move][i][j] == EmptySquare) {
16959                 emptycount++;
16960             } else { ChessSquare piece = boards[move][i][j];
16961                 if (emptycount > 0) {
16962                     if(emptycount<10) /* [HGM] can be >= 10 */
16963                         *p++ = '0' + emptycount;
16964                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16965                     emptycount = 0;
16966                 }
16967                 if(PieceToChar(piece) == '+') {
16968                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16969                     *p++ = '+';
16970                     piece = (ChessSquare)(DEMOTED piece);
16971                 }
16972                 *p++ = PieceToChar(piece);
16973                 if(p[-1] == '~') {
16974                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16975                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16976                     *p++ = '~';
16977                 }
16978             }
16979         }
16980         if (emptycount > 0) {
16981             if(emptycount<10) /* [HGM] can be >= 10 */
16982                 *p++ = '0' + emptycount;
16983             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16984             emptycount = 0;
16985         }
16986         *p++ = '/';
16987     }
16988     *(p - 1) = ' ';
16989
16990     /* [HGM] print Crazyhouse or Shogi holdings */
16991     if( gameInfo.holdingsWidth ) {
16992         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16993         q = p;
16994         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16995             piece = boards[move][i][BOARD_WIDTH-1];
16996             if( piece != EmptySquare )
16997               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16998                   *p++ = PieceToChar(piece);
16999         }
17000         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17001             piece = boards[move][BOARD_HEIGHT-i-1][0];
17002             if( piece != EmptySquare )
17003               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17004                   *p++ = PieceToChar(piece);
17005         }
17006
17007         if( q == p ) *p++ = '-';
17008         *p++ = ']';
17009         *p++ = ' ';
17010     }
17011
17012     /* Active color */
17013     *p++ = whiteToPlay ? 'w' : 'b';
17014     *p++ = ' ';
17015
17016   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17017     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17018   } else {
17019   if(nrCastlingRights) {
17020      q = p;
17021      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17022        /* [HGM] write directly from rights */
17023            if(boards[move][CASTLING][2] != NoRights &&
17024               boards[move][CASTLING][0] != NoRights   )
17025                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17026            if(boards[move][CASTLING][2] != NoRights &&
17027               boards[move][CASTLING][1] != NoRights   )
17028                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17029            if(boards[move][CASTLING][5] != NoRights &&
17030               boards[move][CASTLING][3] != NoRights   )
17031                 *p++ = boards[move][CASTLING][3] + AAA;
17032            if(boards[move][CASTLING][5] != NoRights &&
17033               boards[move][CASTLING][4] != NoRights   )
17034                 *p++ = boards[move][CASTLING][4] + AAA;
17035      } else {
17036
17037         /* [HGM] write true castling rights */
17038         if( nrCastlingRights == 6 ) {
17039             int q, k=0;
17040             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17041                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17042             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17043                  boards[move][CASTLING][2] != NoRights  );
17044             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17045                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17046                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17047                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17048                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17049             }
17050             if(q) *p++ = 'Q';
17051             k = 0;
17052             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17053                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17054             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17055                  boards[move][CASTLING][5] != NoRights  );
17056             if(gameInfo.variant == VariantSChess) {
17057                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17058                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17059                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17060                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17061             }
17062             if(q) *p++ = 'q';
17063         }
17064      }
17065      if (q == p) *p++ = '-'; /* No castling rights */
17066      *p++ = ' ';
17067   }
17068
17069   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17070      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17071     /* En passant target square */
17072     if (move > backwardMostMove) {
17073         fromX = moveList[move - 1][0] - AAA;
17074         fromY = moveList[move - 1][1] - ONE;
17075         toX = moveList[move - 1][2] - AAA;
17076         toY = moveList[move - 1][3] - ONE;
17077         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17078             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17079             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17080             fromX == toX) {
17081             /* 2-square pawn move just happened */
17082             *p++ = toX + AAA;
17083             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17084         } else {
17085             *p++ = '-';
17086         }
17087     } else if(move == backwardMostMove) {
17088         // [HGM] perhaps we should always do it like this, and forget the above?
17089         if((signed char)boards[move][EP_STATUS] >= 0) {
17090             *p++ = boards[move][EP_STATUS] + AAA;
17091             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17092         } else {
17093             *p++ = '-';
17094         }
17095     } else {
17096         *p++ = '-';
17097     }
17098     *p++ = ' ';
17099   }
17100   }
17101
17102     /* [HGM] find reversible plies */
17103     {   int i = 0, j=move;
17104
17105         if (appData.debugMode) { int k;
17106             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17107             for(k=backwardMostMove; k<=forwardMostMove; k++)
17108                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17109
17110         }
17111
17112         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17113         if( j == backwardMostMove ) i += initialRulePlies;
17114         sprintf(p, "%d ", i);
17115         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17116     }
17117     /* Fullmove number */
17118     sprintf(p, "%d", (move / 2) + 1);
17119
17120     return StrSave(buf);
17121 }
17122
17123 Boolean
17124 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17125 {
17126     int i, j;
17127     char *p, c;
17128     int emptycount, virgin[BOARD_FILES];
17129     ChessSquare piece;
17130
17131     p = fen;
17132
17133     /* [HGM] by default clear Crazyhouse holdings, if present */
17134     if(gameInfo.holdingsWidth) {
17135        for(i=0; i<BOARD_HEIGHT; i++) {
17136            board[i][0]             = EmptySquare; /* black holdings */
17137            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17138            board[i][1]             = (ChessSquare) 0; /* black counts */
17139            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17140        }
17141     }
17142
17143     /* Piece placement data */
17144     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17145         j = 0;
17146         for (;;) {
17147             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17148                 if (*p == '/') p++;
17149                 emptycount = gameInfo.boardWidth - j;
17150                 while (emptycount--)
17151                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17152                 break;
17153 #if(BOARD_FILES >= 10)
17154             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17155                 p++; emptycount=10;
17156                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17157                 while (emptycount--)
17158                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17159 #endif
17160             } else if (isdigit(*p)) {
17161                 emptycount = *p++ - '0';
17162                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17163                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17164                 while (emptycount--)
17165                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17166             } else if (*p == '+' || isalpha(*p)) {
17167                 if (j >= gameInfo.boardWidth) return FALSE;
17168                 if(*p=='+') {
17169                     piece = CharToPiece(*++p);
17170                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17171                     piece = (ChessSquare) (PROMOTED piece ); p++;
17172                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17173                 } else piece = CharToPiece(*p++);
17174
17175                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17176                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17177                     piece = (ChessSquare) (PROMOTED piece);
17178                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17179                     p++;
17180                 }
17181                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17182             } else {
17183                 return FALSE;
17184             }
17185         }
17186     }
17187     while (*p == '/' || *p == ' ') p++;
17188
17189     /* [HGM] look for Crazyhouse holdings here */
17190     while(*p==' ') p++;
17191     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17192         if(*p == '[') p++;
17193         if(*p == '-' ) p++; /* empty holdings */ else {
17194             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17195             /* if we would allow FEN reading to set board size, we would   */
17196             /* have to add holdings and shift the board read so far here   */
17197             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17198                 p++;
17199                 if((int) piece >= (int) BlackPawn ) {
17200                     i = (int)piece - (int)BlackPawn;
17201                     i = PieceToNumber((ChessSquare)i);
17202                     if( i >= gameInfo.holdingsSize ) return FALSE;
17203                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17204                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17205                 } else {
17206                     i = (int)piece - (int)WhitePawn;
17207                     i = PieceToNumber((ChessSquare)i);
17208                     if( i >= gameInfo.holdingsSize ) return FALSE;
17209                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17210                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17211                 }
17212             }
17213         }
17214         if(*p == ']') p++;
17215     }
17216
17217     while(*p == ' ') p++;
17218
17219     /* Active color */
17220     c = *p++;
17221     if(appData.colorNickNames) {
17222       if( c == appData.colorNickNames[0] ) c = 'w'; else
17223       if( c == appData.colorNickNames[1] ) c = 'b';
17224     }
17225     switch (c) {
17226       case 'w':
17227         *blackPlaysFirst = FALSE;
17228         break;
17229       case 'b':
17230         *blackPlaysFirst = TRUE;
17231         break;
17232       default:
17233         return FALSE;
17234     }
17235
17236     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17237     /* return the extra info in global variiables             */
17238
17239     /* set defaults in case FEN is incomplete */
17240     board[EP_STATUS] = EP_UNKNOWN;
17241     for(i=0; i<nrCastlingRights; i++ ) {
17242         board[CASTLING][i] =
17243             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17244     }   /* assume possible unless obviously impossible */
17245     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17246     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17247     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17248                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17249     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17250     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17251     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17252                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17253     FENrulePlies = 0;
17254
17255     while(*p==' ') p++;
17256     if(nrCastlingRights) {
17257       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17258       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17259           /* castling indicator present, so default becomes no castlings */
17260           for(i=0; i<nrCastlingRights; i++ ) {
17261                  board[CASTLING][i] = NoRights;
17262           }
17263       }
17264       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17265              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17266              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17267              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17268         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17269
17270         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17271             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17272             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17273         }
17274         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17275             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17276         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17277                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17278         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17279                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17280         switch(c) {
17281           case'K':
17282               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17283               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17284               board[CASTLING][2] = whiteKingFile;
17285               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17286               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17287               break;
17288           case'Q':
17289               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17290               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17291               board[CASTLING][2] = whiteKingFile;
17292               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17293               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17294               break;
17295           case'k':
17296               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17297               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17298               board[CASTLING][5] = blackKingFile;
17299               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17300               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17301               break;
17302           case'q':
17303               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17304               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17305               board[CASTLING][5] = blackKingFile;
17306               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17307               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17308           case '-':
17309               break;
17310           default: /* FRC castlings */
17311               if(c >= 'a') { /* black rights */
17312                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17313                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17314                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17315                   if(i == BOARD_RGHT) break;
17316                   board[CASTLING][5] = i;
17317                   c -= AAA;
17318                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17319                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17320                   if(c > i)
17321                       board[CASTLING][3] = c;
17322                   else
17323                       board[CASTLING][4] = c;
17324               } else { /* white rights */
17325                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17326                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17327                     if(board[0][i] == WhiteKing) break;
17328                   if(i == BOARD_RGHT) break;
17329                   board[CASTLING][2] = i;
17330                   c -= AAA - 'a' + 'A';
17331                   if(board[0][c] >= WhiteKing) break;
17332                   if(c > i)
17333                       board[CASTLING][0] = c;
17334                   else
17335                       board[CASTLING][1] = c;
17336               }
17337         }
17338       }
17339       for(i=0; i<nrCastlingRights; i++)
17340         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17341       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17342     if (appData.debugMode) {
17343         fprintf(debugFP, "FEN castling rights:");
17344         for(i=0; i<nrCastlingRights; i++)
17345         fprintf(debugFP, " %d", board[CASTLING][i]);
17346         fprintf(debugFP, "\n");
17347     }
17348
17349       while(*p==' ') p++;
17350     }
17351
17352     /* read e.p. field in games that know e.p. capture */
17353     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17354        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17355       if(*p=='-') {
17356         p++; board[EP_STATUS] = EP_NONE;
17357       } else {
17358          char c = *p++ - AAA;
17359
17360          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17361          if(*p >= '0' && *p <='9') p++;
17362          board[EP_STATUS] = c;
17363       }
17364     }
17365
17366
17367     if(sscanf(p, "%d", &i) == 1) {
17368         FENrulePlies = i; /* 50-move ply counter */
17369         /* (The move number is still ignored)    */
17370     }
17371
17372     return TRUE;
17373 }
17374
17375 void
17376 EditPositionPasteFEN (char *fen)
17377 {
17378   if (fen != NULL) {
17379     Board initial_position;
17380
17381     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17382       DisplayError(_("Bad FEN position in clipboard"), 0);
17383       return ;
17384     } else {
17385       int savedBlackPlaysFirst = blackPlaysFirst;
17386       EditPositionEvent();
17387       blackPlaysFirst = savedBlackPlaysFirst;
17388       CopyBoard(boards[0], initial_position);
17389       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17390       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17391       DisplayBothClocks();
17392       DrawPosition(FALSE, boards[currentMove]);
17393     }
17394   }
17395 }
17396
17397 static char cseq[12] = "\\   ";
17398
17399 Boolean
17400 set_cont_sequence (char *new_seq)
17401 {
17402     int len;
17403     Boolean ret;
17404
17405     // handle bad attempts to set the sequence
17406         if (!new_seq)
17407                 return 0; // acceptable error - no debug
17408
17409     len = strlen(new_seq);
17410     ret = (len > 0) && (len < sizeof(cseq));
17411     if (ret)
17412       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17413     else if (appData.debugMode)
17414       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17415     return ret;
17416 }
17417
17418 /*
17419     reformat a source message so words don't cross the width boundary.  internal
17420     newlines are not removed.  returns the wrapped size (no null character unless
17421     included in source message).  If dest is NULL, only calculate the size required
17422     for the dest buffer.  lp argument indicats line position upon entry, and it's
17423     passed back upon exit.
17424 */
17425 int
17426 wrap (char *dest, char *src, int count, int width, int *lp)
17427 {
17428     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17429
17430     cseq_len = strlen(cseq);
17431     old_line = line = *lp;
17432     ansi = len = clen = 0;
17433
17434     for (i=0; i < count; i++)
17435     {
17436         if (src[i] == '\033')
17437             ansi = 1;
17438
17439         // if we hit the width, back up
17440         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17441         {
17442             // store i & len in case the word is too long
17443             old_i = i, old_len = len;
17444
17445             // find the end of the last word
17446             while (i && src[i] != ' ' && src[i] != '\n')
17447             {
17448                 i--;
17449                 len--;
17450             }
17451
17452             // word too long?  restore i & len before splitting it
17453             if ((old_i-i+clen) >= width)
17454             {
17455                 i = old_i;
17456                 len = old_len;
17457             }
17458
17459             // extra space?
17460             if (i && src[i-1] == ' ')
17461                 len--;
17462
17463             if (src[i] != ' ' && src[i] != '\n')
17464             {
17465                 i--;
17466                 if (len)
17467                     len--;
17468             }
17469
17470             // now append the newline and continuation sequence
17471             if (dest)
17472                 dest[len] = '\n';
17473             len++;
17474             if (dest)
17475                 strncpy(dest+len, cseq, cseq_len);
17476             len += cseq_len;
17477             line = cseq_len;
17478             clen = cseq_len;
17479             continue;
17480         }
17481
17482         if (dest)
17483             dest[len] = src[i];
17484         len++;
17485         if (!ansi)
17486             line++;
17487         if (src[i] == '\n')
17488             line = 0;
17489         if (src[i] == 'm')
17490             ansi = 0;
17491     }
17492     if (dest && appData.debugMode)
17493     {
17494         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17495             count, width, line, len, *lp);
17496         show_bytes(debugFP, src, count);
17497         fprintf(debugFP, "\ndest: ");
17498         show_bytes(debugFP, dest, len);
17499         fprintf(debugFP, "\n");
17500     }
17501     *lp = dest ? line : old_line;
17502
17503     return len;
17504 }
17505
17506 // [HGM] vari: routines for shelving variations
17507 Boolean modeRestore = FALSE;
17508
17509 void
17510 PushInner (int firstMove, int lastMove)
17511 {
17512         int i, j, nrMoves = lastMove - firstMove;
17513
17514         // push current tail of game on stack
17515         savedResult[storedGames] = gameInfo.result;
17516         savedDetails[storedGames] = gameInfo.resultDetails;
17517         gameInfo.resultDetails = NULL;
17518         savedFirst[storedGames] = firstMove;
17519         savedLast [storedGames] = lastMove;
17520         savedFramePtr[storedGames] = framePtr;
17521         framePtr -= nrMoves; // reserve space for the boards
17522         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17523             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17524             for(j=0; j<MOVE_LEN; j++)
17525                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17526             for(j=0; j<2*MOVE_LEN; j++)
17527                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17528             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17529             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17530             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17531             pvInfoList[firstMove+i-1].depth = 0;
17532             commentList[framePtr+i] = commentList[firstMove+i];
17533             commentList[firstMove+i] = NULL;
17534         }
17535
17536         storedGames++;
17537         forwardMostMove = firstMove; // truncate game so we can start variation
17538 }
17539
17540 void
17541 PushTail (int firstMove, int lastMove)
17542 {
17543         if(appData.icsActive) { // only in local mode
17544                 forwardMostMove = currentMove; // mimic old ICS behavior
17545                 return;
17546         }
17547         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17548
17549         PushInner(firstMove, lastMove);
17550         if(storedGames == 1) GreyRevert(FALSE);
17551         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17552 }
17553
17554 void
17555 PopInner (Boolean annotate)
17556 {
17557         int i, j, nrMoves;
17558         char buf[8000], moveBuf[20];
17559
17560         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17561         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17562         nrMoves = savedLast[storedGames] - currentMove;
17563         if(annotate) {
17564                 int cnt = 10;
17565                 if(!WhiteOnMove(currentMove))
17566                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17567                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17568                 for(i=currentMove; i<forwardMostMove; i++) {
17569                         if(WhiteOnMove(i))
17570                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17571                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17572                         strcat(buf, moveBuf);
17573                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17574                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17575                 }
17576                 strcat(buf, ")");
17577         }
17578         for(i=1; i<=nrMoves; i++) { // copy last variation back
17579             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17580             for(j=0; j<MOVE_LEN; j++)
17581                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17582             for(j=0; j<2*MOVE_LEN; j++)
17583                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17584             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17585             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17586             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17587             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17588             commentList[currentMove+i] = commentList[framePtr+i];
17589             commentList[framePtr+i] = NULL;
17590         }
17591         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17592         framePtr = savedFramePtr[storedGames];
17593         gameInfo.result = savedResult[storedGames];
17594         if(gameInfo.resultDetails != NULL) {
17595             free(gameInfo.resultDetails);
17596       }
17597         gameInfo.resultDetails = savedDetails[storedGames];
17598         forwardMostMove = currentMove + nrMoves;
17599 }
17600
17601 Boolean
17602 PopTail (Boolean annotate)
17603 {
17604         if(appData.icsActive) return FALSE; // only in local mode
17605         if(!storedGames) return FALSE; // sanity
17606         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17607
17608         PopInner(annotate);
17609         if(currentMove < forwardMostMove) ForwardEvent(); else
17610         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17611
17612         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17613         return TRUE;
17614 }
17615
17616 void
17617 CleanupTail ()
17618 {       // remove all shelved variations
17619         int i;
17620         for(i=0; i<storedGames; i++) {
17621             if(savedDetails[i])
17622                 free(savedDetails[i]);
17623             savedDetails[i] = NULL;
17624         }
17625         for(i=framePtr; i<MAX_MOVES; i++) {
17626                 if(commentList[i]) free(commentList[i]);
17627                 commentList[i] = NULL;
17628         }
17629         framePtr = MAX_MOVES-1;
17630         storedGames = 0;
17631 }
17632
17633 void
17634 LoadVariation (int index, char *text)
17635 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17636         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17637         int level = 0, move;
17638
17639         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17640         // first find outermost bracketing variation
17641         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17642             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17643                 if(*p == '{') wait = '}'; else
17644                 if(*p == '[') wait = ']'; else
17645                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17646                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17647             }
17648             if(*p == wait) wait = NULLCHAR; // closing ]} found
17649             p++;
17650         }
17651         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17652         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17653         end[1] = NULLCHAR; // clip off comment beyond variation
17654         ToNrEvent(currentMove-1);
17655         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17656         // kludge: use ParsePV() to append variation to game
17657         move = currentMove;
17658         ParsePV(start, TRUE, TRUE);
17659         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17660         ClearPremoveHighlights();
17661         CommentPopDown();
17662         ToNrEvent(currentMove+1);
17663 }
17664
17665 void
17666 LoadTheme ()
17667 {
17668     char *p, *q, buf[MSG_SIZ];
17669     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17670         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17671         ParseArgsFromString(buf);
17672         ActivateTheme(TRUE); // also redo colors
17673         return;
17674     }
17675     p = nickName;
17676     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17677     {
17678         int len;
17679         q = appData.themeNames;
17680         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17681       if(appData.useBitmaps) {
17682         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17683                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17684                 appData.liteBackTextureMode,
17685                 appData.darkBackTextureMode );
17686       } else {
17687         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17688                 Col2Text(2),   // lightSquareColor
17689                 Col2Text(3) ); // darkSquareColor
17690       }
17691       if(appData.useBorder) {
17692         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17693                 appData.border);
17694       } else {
17695         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17696       }
17697       if(appData.useFont) {
17698         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17699                 appData.renderPiecesWithFont,
17700                 appData.fontToPieceTable,
17701                 Col2Text(9),    // appData.fontBackColorWhite
17702                 Col2Text(10) ); // appData.fontForeColorBlack
17703       } else {
17704         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17705                 appData.pieceDirectory);
17706         if(!appData.pieceDirectory[0])
17707           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17708                 Col2Text(0),   // whitePieceColor
17709                 Col2Text(1) ); // blackPieceColor
17710       }
17711       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17712                 Col2Text(4),   // highlightSquareColor
17713                 Col2Text(5) ); // premoveHighlightColor
17714         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17715         if(insert != q) insert[-1] = NULLCHAR;
17716         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17717         if(q)   free(q);
17718     }
17719     ActivateTheme(FALSE);
17720 }