Adjudicate pawn-drop mate as loss in Shogi
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 Boolean abortMatch;
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
276
277 /* States for ics_getting_history */
278 #define H_FALSE 0
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
284
285 /* whosays values for GameEnds */
286 #define GE_ICS 0
287 #define GE_ENGINE 1
288 #define GE_PLAYER 2
289 #define GE_FILE 3
290 #define GE_XBOARD 4
291 #define GE_ENGINE1 5
292 #define GE_ENGINE2 6
293
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
296
297 /* Different types of move when calling RegisterMove */
298 #define CMAIL_MOVE   0
299 #define CMAIL_RESIGN 1
300 #define CMAIL_DRAW   2
301 #define CMAIL_ACCEPT 3
302
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
307
308 /* Telnet protocol constants */
309 #define TN_WILL 0373
310 #define TN_WONT 0374
311 #define TN_DO   0375
312 #define TN_DONT 0376
313 #define TN_IAC  0377
314 #define TN_ECHO 0001
315 #define TN_SGA  0003
316 #define TN_PORT 23
317
318 char*
319 safeStrCpy (char *dst, const char *src, size_t count)
320 { // [HGM] made safe
321   int i;
322   assert( dst != NULL );
323   assert( src != NULL );
324   assert( count > 0 );
325
326   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327   if(  i == count && dst[count-1] != NULLCHAR)
328     {
329       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330       if(appData.debugMode)
331       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
332     }
333
334   return dst;
335 }
336
337 /* Some compiler can't cast u64 to double
338  * This function do the job for us:
339
340  * We use the highest bit for cast, this only
341  * works if the highest bit is not
342  * in use (This should not happen)
343  *
344  * We used this for all compiler
345  */
346 double
347 u64ToDouble (u64 value)
348 {
349   double r;
350   u64 tmp = value & u64Const(0x7fffffffffffffff);
351   r = (double)(s64)tmp;
352   if (value & u64Const(0x8000000000000000))
353        r +=  9.2233720368547758080e18; /* 2^63 */
354  return r;
355 }
356
357 /* Fake up flags for now, as we aren't keeping track of castling
358    availability yet. [HGM] Change of logic: the flag now only
359    indicates the type of castlings allowed by the rule of the game.
360    The actual rights themselves are maintained in the array
361    castlingRights, as part of the game history, and are not probed
362    by this function.
363  */
364 int
365 PosFlags (index)
366 {
367   int flags = F_ALL_CASTLE_OK;
368   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369   switch (gameInfo.variant) {
370   case VariantSuicide:
371     flags &= ~F_ALL_CASTLE_OK;
372   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373     flags |= F_IGNORE_CHECK;
374   case VariantLosers:
375     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376     break;
377   case VariantAtomic:
378     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379     break;
380   case VariantKriegspiel:
381     flags |= F_KRIEGSPIEL_CAPTURE;
382     break;
383   case VariantCapaRandom:
384   case VariantFischeRandom:
385     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386   case VariantNoCastle:
387   case VariantShatranj:
388   case VariantCourier:
389   case VariantMakruk:
390   case VariantGrand:
391     flags &= ~F_ALL_CASTLE_OK;
392     break;
393   default:
394     break;
395   }
396   return flags;
397 }
398
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
401
402 /*
403     [AS] Note: sometimes, the sscanf() function is used to parse the input
404     into a fixed-size buffer. Because of this, we must be prepared to
405     receive strings as long as the size of the input buffer, which is currently
406     set to 4K for Windows and 8K for the rest.
407     So, we must either allocate sufficiently large buffers here, or
408     reduce the size of the input buffer in the input reading part.
409 */
410
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
414
415 ChessProgramState first, second, pairing;
416
417 /* premove variables */
418 int premoveToX = 0;
419 int premoveToY = 0;
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
423 int gotPremove = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
426
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
429
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
457
458 int have_sent_ICS_logon = 0;
459 int movesPerSession;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
471
472 /* animateTraining preserves the state of appData.animate
473  * when Training mode is activated. This allows the
474  * response to be animated when appData.animate == TRUE and
475  * appData.animateDragging == TRUE.
476  */
477 Boolean animateTraining;
478
479 GameInfo gameInfo;
480
481 AppData appData;
482
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char  initialRights[BOARD_FILES];
487 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int   initialRulePlies, FENrulePlies;
489 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
490 int loadFlag = 0;
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
493
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
497 int storedGames = 0;
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
503
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
509
510 ChessSquare  FIDEArray[2][BOARD_FILES] = {
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514         BlackKing, BlackBishop, BlackKnight, BlackRook }
515 };
516
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackKing, BlackKnight, BlackRook }
522 };
523
524 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527     { BlackRook, BlackMan, BlackBishop, BlackQueen,
528         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 };
530
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 };
537
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 };
544
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 };
551
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackMan, BlackFerz,
556         BlackKing, BlackMan, BlackKnight, BlackRook }
557 };
558
559
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 };
567
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 };
574
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 };
581
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 };
588
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 };
595
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 };
602
603 #ifdef GOTHIC
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 };
610 #else // !GOTHIC
611 #define GothicArray CapablancaArray
612 #endif // !GOTHIC
613
614 #ifdef FALCON
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
620 };
621 #else // !FALCON
622 #define FalconArray CapablancaArray
623 #endif // !FALCON
624
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
631
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
638 };
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
642
643
644 Board initialPosition;
645
646
647 /* Convert str to a rating. Checks for special cases of "----",
648
649    "++++", etc. Also strips ()'s */
650 int
651 string_to_rating (char *str)
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats ()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit ()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine (ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions (ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("first"),
744   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 N_("second")
747 };
748
749 void
750 InitEngine (ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789     cps->reload = FALSE;
790
791     /* New features added by Tord: */
792     cps->useFEN960 = FALSE;
793     cps->useOOCastle = TRUE;
794     /* End of new features added by Tord. */
795     cps->fenOverride  = appData.fenOverride[n];
796
797     /* [HGM] time odds: set factor for each machine */
798     cps->timeOdds  = appData.timeOdds[n];
799
800     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801     cps->accumulateTC = appData.accumulateTC[n];
802     cps->maxNrOfSessions = 1;
803
804     /* [HGM] debug */
805     cps->debug = FALSE;
806
807     cps->supportsNPS = UNKNOWN;
808     cps->memSize = FALSE;
809     cps->maxCores = FALSE;
810     cps->egtFormats[0] = NULLCHAR;
811
812     /* [HGM] options */
813     cps->optionSettings  = appData.engOptions[n];
814
815     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816     cps->isUCI = appData.isUCI[n]; /* [AS] */
817     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
818
819     if (appData.protocolVersion[n] > PROTOVER
820         || appData.protocolVersion[n] < 1)
821       {
822         char buf[MSG_SIZ];
823         int len;
824
825         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826                        appData.protocolVersion[n]);
827         if( (len >= MSG_SIZ) && appData.debugMode )
828           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
829
830         DisplayFatalError(buf, 0, 2);
831       }
832     else
833       {
834         cps->protocolVersion = appData.protocolVersion[n];
835       }
836
837     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
838     ParseFeatures(appData.featureDefaults, cps);
839 }
840
841 ChessProgramState *savCps;
842
843 void
844 LoadEngine ()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine (ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] =
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
883         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
884         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
885
886 void
887 FloatToFront(char **list, char *engineLine)
888 {
889     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
890     int i=0;
891     if(appData.recentEngines <= 0) return;
892     TidyProgramName(engineLine, "localhost", tidy+1);
893     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
894     strncpy(buf+1, *list, MSG_SIZ-50);
895     if(p = strstr(buf, tidy)) { // tidy name appears in list
896         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
897         while(*p++ = *++q); // squeeze out
898     }
899     strcat(tidy, buf+1); // put list behind tidy name
900     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
901     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
902     ASSIGN(*list, tidy+1);
903 }
904
905 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
906
907 void
908 Load (ChessProgramState *cps, int i)
909 {
910     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
911     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
912         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
913         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
914         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
915         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
916         appData.firstProtocolVersion = PROTOVER;
917         ParseArgsFromString(buf);
918         SwapEngines(i);
919         ReplaceEngine(cps, i);
920         FloatToFront(&appData.recentEngineList, engineLine);
921         return;
922     }
923     p = engineName;
924     while(q = strchr(p, SLASH)) p = q+1;
925     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
926     if(engineDir[0] != NULLCHAR) {
927         ASSIGN(appData.directory[i], engineDir); p = engineName;
928     } else if(p != engineName) { // derive directory from engine path, when not given
929         p[-1] = 0;
930         ASSIGN(appData.directory[i], engineName);
931         p[-1] = SLASH;
932         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
933     } else { ASSIGN(appData.directory[i], "."); }
934     if(params[0]) {
935         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
936         snprintf(command, MSG_SIZ, "%s %s", p, params);
937         p = command;
938     }
939     ASSIGN(appData.chessProgram[i], p);
940     appData.isUCI[i] = isUCI;
941     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
942     appData.hasOwnBookUCI[i] = hasBook;
943     if(!nickName[0]) useNick = FALSE;
944     if(useNick) ASSIGN(appData.pgnName[i], nickName);
945     if(addToList) {
946         int len;
947         char quote;
948         q = firstChessProgramNames;
949         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
950         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
951         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
952                         quote, p, quote, appData.directory[i],
953                         useNick ? " -fn \"" : "",
954                         useNick ? nickName : "",
955                         useNick ? "\"" : "",
956                         v1 ? " -firstProtocolVersion 1" : "",
957                         hasBook ? "" : " -fNoOwnBookUCI",
958                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
959                         storeVariant ? " -variant " : "",
960                         storeVariant ? VariantName(gameInfo.variant) : "");
961         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
962         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
963         if(insert != q) insert[-1] = NULLCHAR;
964         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
965         if(q)   free(q);
966         FloatToFront(&appData.recentEngineList, buf);
967     }
968     ReplaceEngine(cps, i);
969 }
970
971 void
972 InitTimeControls ()
973 {
974     int matched, min, sec;
975     /*
976      * Parse timeControl resource
977      */
978     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
979                           appData.movesPerSession)) {
980         char buf[MSG_SIZ];
981         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
982         DisplayFatalError(buf, 0, 2);
983     }
984
985     /*
986      * Parse searchTime resource
987      */
988     if (*appData.searchTime != NULLCHAR) {
989         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
990         if (matched == 1) {
991             searchTime = min * 60;
992         } else if (matched == 2) {
993             searchTime = min * 60 + sec;
994         } else {
995             char buf[MSG_SIZ];
996             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
997             DisplayFatalError(buf, 0, 2);
998         }
999     }
1000 }
1001
1002 void
1003 InitBackEnd1 ()
1004 {
1005
1006     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1007     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1008
1009     GetTimeMark(&programStartTime);
1010     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1011     appData.seedBase = random() + (random()<<15);
1012     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1013
1014     ClearProgramStats();
1015     programStats.ok_to_send = 1;
1016     programStats.seen_stat = 0;
1017
1018     /*
1019      * Initialize game list
1020      */
1021     ListNew(&gameList);
1022
1023
1024     /*
1025      * Internet chess server status
1026      */
1027     if (appData.icsActive) {
1028         appData.matchMode = FALSE;
1029         appData.matchGames = 0;
1030 #if ZIPPY
1031         appData.noChessProgram = !appData.zippyPlay;
1032 #else
1033         appData.zippyPlay = FALSE;
1034         appData.zippyTalk = FALSE;
1035         appData.noChessProgram = TRUE;
1036 #endif
1037         if (*appData.icsHelper != NULLCHAR) {
1038             appData.useTelnet = TRUE;
1039             appData.telnetProgram = appData.icsHelper;
1040         }
1041     } else {
1042         appData.zippyTalk = appData.zippyPlay = FALSE;
1043     }
1044
1045     /* [AS] Initialize pv info list [HGM] and game state */
1046     {
1047         int i, j;
1048
1049         for( i=0; i<=framePtr; i++ ) {
1050             pvInfoList[i].depth = -1;
1051             boards[i][EP_STATUS] = EP_NONE;
1052             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1053         }
1054     }
1055
1056     InitTimeControls();
1057
1058     /* [AS] Adjudication threshold */
1059     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1060
1061     InitEngine(&first, 0);
1062     InitEngine(&second, 1);
1063     CommonEngineInit();
1064
1065     pairing.which = "pairing"; // pairing engine
1066     pairing.pr = NoProc;
1067     pairing.isr = NULL;
1068     pairing.program = appData.pairingEngine;
1069     pairing.host = "localhost";
1070     pairing.dir = ".";
1071
1072     if (appData.icsActive) {
1073         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1074     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1075         appData.clockMode = FALSE;
1076         first.sendTime = second.sendTime = 0;
1077     }
1078
1079 #if ZIPPY
1080     /* Override some settings from environment variables, for backward
1081        compatibility.  Unfortunately it's not feasible to have the env
1082        vars just set defaults, at least in xboard.  Ugh.
1083     */
1084     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1085       ZippyInit();
1086     }
1087 #endif
1088
1089     if (!appData.icsActive) {
1090       char buf[MSG_SIZ];
1091       int len;
1092
1093       /* Check for variants that are supported only in ICS mode,
1094          or not at all.  Some that are accepted here nevertheless
1095          have bugs; see comments below.
1096       */
1097       VariantClass variant = StringToVariant(appData.variant);
1098       switch (variant) {
1099       case VariantBughouse:     /* need four players and two boards */
1100       case VariantKriegspiel:   /* need to hide pieces and move details */
1101         /* case VariantFischeRandom: (Fabien: moved below) */
1102         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1103         if( (len >= MSG_SIZ) && appData.debugMode )
1104           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1105
1106         DisplayFatalError(buf, 0, 2);
1107         return;
1108
1109       case VariantUnknown:
1110       case VariantLoadable:
1111       case Variant29:
1112       case Variant30:
1113       case Variant31:
1114       case Variant32:
1115       case Variant33:
1116       case Variant34:
1117       case Variant35:
1118       case Variant36:
1119       default:
1120         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1121         if( (len >= MSG_SIZ) && appData.debugMode )
1122           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1123
1124         DisplayFatalError(buf, 0, 2);
1125         return;
1126
1127       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1128       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1129       case VariantGothic:     /* [HGM] should work */
1130       case VariantCapablanca: /* [HGM] should work */
1131       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1132       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1133       case VariantKnightmate: /* [HGM] should work */
1134       case VariantCylinder:   /* [HGM] untested */
1135       case VariantFalcon:     /* [HGM] untested */
1136       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1137                                  offboard interposition not understood */
1138       case VariantNormal:     /* definitely works! */
1139       case VariantWildCastle: /* pieces not automatically shuffled */
1140       case VariantNoCastle:   /* pieces not automatically shuffled */
1141       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1142       case VariantLosers:     /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantSuicide:    /* should work except for win condition,
1145                                  and doesn't know captures are mandatory */
1146       case VariantGiveaway:   /* should work except for win condition,
1147                                  and doesn't know captures are mandatory */
1148       case VariantTwoKings:   /* should work */
1149       case VariantAtomic:     /* should work except for win condition */
1150       case Variant3Check:     /* should work except for win condition */
1151       case VariantShatranj:   /* should work except for all win conditions */
1152       case VariantMakruk:     /* should work except for draw countdown */
1153       case VariantBerolina:   /* might work if TestLegality is off */
1154       case VariantCapaRandom: /* should work */
1155       case VariantJanus:      /* should work */
1156       case VariantSuper:      /* experimental */
1157       case VariantGreat:      /* experimental, requires legality testing to be off */
1158       case VariantSChess:     /* S-Chess, should work */
1159       case VariantGrand:      /* should work */
1160       case VariantSpartan:    /* should work */
1161         break;
1162       }
1163     }
1164
1165 }
1166
1167 int
1168 NextIntegerFromString (char ** str, long * value)
1169 {
1170     int result = -1;
1171     char * s = *str;
1172
1173     while( *s == ' ' || *s == '\t' ) {
1174         s++;
1175     }
1176
1177     *value = 0;
1178
1179     if( *s >= '0' && *s <= '9' ) {
1180         while( *s >= '0' && *s <= '9' ) {
1181             *value = *value * 10 + (*s - '0');
1182             s++;
1183         }
1184
1185         result = 0;
1186     }
1187
1188     *str = s;
1189
1190     return result;
1191 }
1192
1193 int
1194 NextTimeControlFromString (char ** str, long * value)
1195 {
1196     long temp;
1197     int result = NextIntegerFromString( str, &temp );
1198
1199     if( result == 0 ) {
1200         *value = temp * 60; /* Minutes */
1201         if( **str == ':' ) {
1202             (*str)++;
1203             result = NextIntegerFromString( str, &temp );
1204             *value += temp; /* Seconds */
1205         }
1206     }
1207
1208     return result;
1209 }
1210
1211 int
1212 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1213 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1214     int result = -1, type = 0; long temp, temp2;
1215
1216     if(**str != ':') return -1; // old params remain in force!
1217     (*str)++;
1218     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1219     if( NextIntegerFromString( str, &temp ) ) return -1;
1220     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1221
1222     if(**str != '/') {
1223         /* time only: incremental or sudden-death time control */
1224         if(**str == '+') { /* increment follows; read it */
1225             (*str)++;
1226             if(**str == '!') type = *(*str)++; // Bronstein TC
1227             if(result = NextIntegerFromString( str, &temp2)) return -1;
1228             *inc = temp2 * 1000;
1229             if(**str == '.') { // read fraction of increment
1230                 char *start = ++(*str);
1231                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1232                 temp2 *= 1000;
1233                 while(start++ < *str) temp2 /= 10;
1234                 *inc += temp2;
1235             }
1236         } else *inc = 0;
1237         *moves = 0; *tc = temp * 1000; *incType = type;
1238         return 0;
1239     }
1240
1241     (*str)++; /* classical time control */
1242     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1243
1244     if(result == 0) {
1245         *moves = temp;
1246         *tc    = temp2 * 1000;
1247         *inc   = 0;
1248         *incType = type;
1249     }
1250     return result;
1251 }
1252
1253 int
1254 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1255 {   /* [HGM] get time to add from the multi-session time-control string */
1256     int incType, moves=1; /* kludge to force reading of first session */
1257     long time, increment;
1258     char *s = tcString;
1259
1260     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1261     do {
1262         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1263         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1264         if(movenr == -1) return time;    /* last move before new session     */
1265         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1266         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1267         if(!moves) return increment;     /* current session is incremental   */
1268         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1269     } while(movenr >= -1);               /* try again for next session       */
1270
1271     return 0; // no new time quota on this move
1272 }
1273
1274 int
1275 ParseTimeControl (char *tc, float ti, int mps)
1276 {
1277   long tc1;
1278   long tc2;
1279   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1280   int min, sec=0;
1281
1282   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1283   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1284       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1285   if(ti > 0) {
1286
1287     if(mps)
1288       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1289     else
1290       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1291   } else {
1292     if(mps)
1293       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1294     else
1295       snprintf(buf, MSG_SIZ, ":%s", mytc);
1296   }
1297   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1298
1299   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1300     return FALSE;
1301   }
1302
1303   if( *tc == '/' ) {
1304     /* Parse second time control */
1305     tc++;
1306
1307     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1308       return FALSE;
1309     }
1310
1311     if( tc2 == 0 ) {
1312       return FALSE;
1313     }
1314
1315     timeControl_2 = tc2 * 1000;
1316   }
1317   else {
1318     timeControl_2 = 0;
1319   }
1320
1321   if( tc1 == 0 ) {
1322     return FALSE;
1323   }
1324
1325   timeControl = tc1 * 1000;
1326
1327   if (ti >= 0) {
1328     timeIncrement = ti * 1000;  /* convert to ms */
1329     movesPerSession = 0;
1330   } else {
1331     timeIncrement = 0;
1332     movesPerSession = mps;
1333   }
1334   return TRUE;
1335 }
1336
1337 void
1338 InitBackEnd2 ()
1339 {
1340     if (appData.debugMode) {
1341         fprintf(debugFP, "%s\n", programVersion);
1342     }
1343     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1344
1345     set_cont_sequence(appData.wrapContSeq);
1346     if (appData.matchGames > 0) {
1347         appData.matchMode = TRUE;
1348     } else if (appData.matchMode) {
1349         appData.matchGames = 1;
1350     }
1351     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1352         appData.matchGames = appData.sameColorGames;
1353     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1354         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1355         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1356     }
1357     Reset(TRUE, FALSE);
1358     if (appData.noChessProgram || first.protocolVersion == 1) {
1359       InitBackEnd3();
1360     } else {
1361       /* kludge: allow timeout for initial "feature" commands */
1362       FreezeUI();
1363       DisplayMessage("", _("Starting chess program"));
1364       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1365     }
1366 }
1367
1368 int
1369 CalculateIndex (int index, int gameNr)
1370 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1371     int res;
1372     if(index > 0) return index; // fixed nmber
1373     if(index == 0) return 1;
1374     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1375     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1376     return res;
1377 }
1378
1379 int
1380 LoadGameOrPosition (int gameNr)
1381 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1382     if (*appData.loadGameFile != NULLCHAR) {
1383         if (!LoadGameFromFile(appData.loadGameFile,
1384                 CalculateIndex(appData.loadGameIndex, gameNr),
1385                               appData.loadGameFile, FALSE)) {
1386             DisplayFatalError(_("Bad game file"), 0, 1);
1387             return 0;
1388         }
1389     } else if (*appData.loadPositionFile != NULLCHAR) {
1390         if (!LoadPositionFromFile(appData.loadPositionFile,
1391                 CalculateIndex(appData.loadPositionIndex, gameNr),
1392                                   appData.loadPositionFile)) {
1393             DisplayFatalError(_("Bad position file"), 0, 1);
1394             return 0;
1395         }
1396     }
1397     return 1;
1398 }
1399
1400 void
1401 ReserveGame (int gameNr, char resChar)
1402 {
1403     FILE *tf = fopen(appData.tourneyFile, "r+");
1404     char *p, *q, c, buf[MSG_SIZ];
1405     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1406     safeStrCpy(buf, lastMsg, MSG_SIZ);
1407     DisplayMessage(_("Pick new game"), "");
1408     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1409     ParseArgsFromFile(tf);
1410     p = q = appData.results;
1411     if(appData.debugMode) {
1412       char *r = appData.participants;
1413       fprintf(debugFP, "results = '%s'\n", p);
1414       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1415       fprintf(debugFP, "\n");
1416     }
1417     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1418     nextGame = q - p;
1419     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1420     safeStrCpy(q, p, strlen(p) + 2);
1421     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1422     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1424         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1425         q[nextGame] = '*';
1426     }
1427     fseek(tf, -(strlen(p)+4), SEEK_END);
1428     c = fgetc(tf);
1429     if(c != '"') // depending on DOS or Unix line endings we can be one off
1430          fseek(tf, -(strlen(p)+2), SEEK_END);
1431     else fseek(tf, -(strlen(p)+3), SEEK_END);
1432     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1433     DisplayMessage(buf, "");
1434     free(p); appData.results = q;
1435     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1436        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1437       int round = appData.defaultMatchGames * appData.tourneyType;
1438       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1439          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1440         UnloadEngine(&first);  // next game belongs to other pairing;
1441         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1442     }
1443     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1444 }
1445
1446 void
1447 MatchEvent (int mode)
1448 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1449         int dummy;
1450         if(matchMode) { // already in match mode: switch it off
1451             abortMatch = TRUE;
1452             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1453             return;
1454         }
1455 //      if(gameMode != BeginningOfGame) {
1456 //          DisplayError(_("You can only start a match from the initial position."), 0);
1457 //          return;
1458 //      }
1459         abortMatch = FALSE;
1460         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1461         /* Set up machine vs. machine match */
1462         nextGame = 0;
1463         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1464         if(appData.tourneyFile[0]) {
1465             ReserveGame(-1, 0);
1466             if(nextGame > appData.matchGames) {
1467                 char buf[MSG_SIZ];
1468                 if(strchr(appData.results, '*') == NULL) {
1469                     FILE *f;
1470                     appData.tourneyCycles++;
1471                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1472                         fclose(f);
1473                         NextTourneyGame(-1, &dummy);
1474                         ReserveGame(-1, 0);
1475                         if(nextGame <= appData.matchGames) {
1476                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1477                             matchMode = mode;
1478                             ScheduleDelayedEvent(NextMatchGame, 10000);
1479                             return;
1480                         }
1481                     }
1482                 }
1483                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1484                 DisplayError(buf, 0);
1485                 appData.tourneyFile[0] = 0;
1486                 return;
1487             }
1488         } else
1489         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1490             DisplayFatalError(_("Can't have a match with no chess programs"),
1491                               0, 2);
1492             return;
1493         }
1494         matchMode = mode;
1495         matchGame = roundNr = 1;
1496         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1497         NextMatchGame();
1498 }
1499
1500 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1501
1502 void
1503 InitBackEnd3 P((void))
1504 {
1505     GameMode initialMode;
1506     char buf[MSG_SIZ];
1507     int err, len;
1508
1509     InitChessProgram(&first, startedFromSetupPosition);
1510
1511     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1512         free(programVersion);
1513         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1514         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1515         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1516     }
1517
1518     if (appData.icsActive) {
1519 #ifdef WIN32
1520         /* [DM] Make a console window if needed [HGM] merged ifs */
1521         ConsoleCreate();
1522 #endif
1523         err = establish();
1524         if (err != 0)
1525           {
1526             if (*appData.icsCommPort != NULLCHAR)
1527               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1528                              appData.icsCommPort);
1529             else
1530               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1531                         appData.icsHost, appData.icsPort);
1532
1533             if( (len >= MSG_SIZ) && appData.debugMode )
1534               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1535
1536             DisplayFatalError(buf, err, 1);
1537             return;
1538         }
1539         SetICSMode();
1540         telnetISR =
1541           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1542         fromUserISR =
1543           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1544         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1545             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1546     } else if (appData.noChessProgram) {
1547         SetNCPMode();
1548     } else {
1549         SetGNUMode();
1550     }
1551
1552     if (*appData.cmailGameName != NULLCHAR) {
1553         SetCmailMode();
1554         OpenLoopback(&cmailPR);
1555         cmailISR =
1556           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1557     }
1558
1559     ThawUI();
1560     DisplayMessage("", "");
1561     if (StrCaseCmp(appData.initialMode, "") == 0) {
1562       initialMode = BeginningOfGame;
1563       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1564         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1565         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1566         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1567         ModeHighlight();
1568       }
1569     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1570       initialMode = TwoMachinesPlay;
1571     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1572       initialMode = AnalyzeFile;
1573     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1574       initialMode = AnalyzeMode;
1575     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1576       initialMode = MachinePlaysWhite;
1577     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1578       initialMode = MachinePlaysBlack;
1579     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1580       initialMode = EditGame;
1581     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1582       initialMode = EditPosition;
1583     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1584       initialMode = Training;
1585     } else {
1586       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1587       if( (len >= MSG_SIZ) && appData.debugMode )
1588         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1589
1590       DisplayFatalError(buf, 0, 2);
1591       return;
1592     }
1593
1594     if (appData.matchMode) {
1595         if(appData.tourneyFile[0]) { // start tourney from command line
1596             FILE *f;
1597             if(f = fopen(appData.tourneyFile, "r")) {
1598                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1599                 fclose(f);
1600                 appData.clockMode = TRUE;
1601                 SetGNUMode();
1602             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1603         }
1604         MatchEvent(TRUE);
1605     } else if (*appData.cmailGameName != NULLCHAR) {
1606         /* Set up cmail mode */
1607         ReloadCmailMsgEvent(TRUE);
1608     } else {
1609         /* Set up other modes */
1610         if (initialMode == AnalyzeFile) {
1611           if (*appData.loadGameFile == NULLCHAR) {
1612             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1613             return;
1614           }
1615         }
1616         if (*appData.loadGameFile != NULLCHAR) {
1617             (void) LoadGameFromFile(appData.loadGameFile,
1618                                     appData.loadGameIndex,
1619                                     appData.loadGameFile, TRUE);
1620         } else if (*appData.loadPositionFile != NULLCHAR) {
1621             (void) LoadPositionFromFile(appData.loadPositionFile,
1622                                         appData.loadPositionIndex,
1623                                         appData.loadPositionFile);
1624             /* [HGM] try to make self-starting even after FEN load */
1625             /* to allow automatic setup of fairy variants with wtm */
1626             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1627                 gameMode = BeginningOfGame;
1628                 setboardSpoiledMachineBlack = 1;
1629             }
1630             /* [HGM] loadPos: make that every new game uses the setup */
1631             /* from file as long as we do not switch variant          */
1632             if(!blackPlaysFirst) {
1633                 startedFromPositionFile = TRUE;
1634                 CopyBoard(filePosition, boards[0]);
1635             }
1636         }
1637         if (initialMode == AnalyzeMode) {
1638           if (appData.noChessProgram) {
1639             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1644             return;
1645           }
1646           AnalyzeModeEvent();
1647         } else if (initialMode == AnalyzeFile) {
1648           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1649           ShowThinkingEvent();
1650           AnalyzeFileEvent();
1651           AnalysisPeriodicEvent(1);
1652         } else if (initialMode == MachinePlaysWhite) {
1653           if (appData.noChessProgram) {
1654             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1655                               0, 2);
1656             return;
1657           }
1658           if (appData.icsActive) {
1659             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1660                               0, 2);
1661             return;
1662           }
1663           MachineWhiteEvent();
1664         } else if (initialMode == MachinePlaysBlack) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           MachineBlackEvent();
1676         } else if (initialMode == TwoMachinesPlay) {
1677           if (appData.noChessProgram) {
1678             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1679                               0, 2);
1680             return;
1681           }
1682           if (appData.icsActive) {
1683             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1684                               0, 2);
1685             return;
1686           }
1687           TwoMachinesEvent();
1688         } else if (initialMode == EditGame) {
1689           EditGameEvent();
1690         } else if (initialMode == EditPosition) {
1691           EditPositionEvent();
1692         } else if (initialMode == Training) {
1693           if (*appData.loadGameFile == NULLCHAR) {
1694             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1695             return;
1696           }
1697           TrainingEvent();
1698         }
1699     }
1700 }
1701
1702 void
1703 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1704 {
1705     DisplayBook(current+1);
1706
1707     MoveHistorySet( movelist, first, last, current, pvInfoList );
1708
1709     EvalGraphSet( first, last, current, pvInfoList );
1710
1711     MakeEngineOutputTitle();
1712 }
1713
1714 /*
1715  * Establish will establish a contact to a remote host.port.
1716  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1717  *  used to talk to the host.
1718  * Returns 0 if okay, error code if not.
1719  */
1720 int
1721 establish ()
1722 {
1723     char buf[MSG_SIZ];
1724
1725     if (*appData.icsCommPort != NULLCHAR) {
1726         /* Talk to the host through a serial comm port */
1727         return OpenCommPort(appData.icsCommPort, &icsPR);
1728
1729     } else if (*appData.gateway != NULLCHAR) {
1730         if (*appData.remoteShell == NULLCHAR) {
1731             /* Use the rcmd protocol to run telnet program on a gateway host */
1732             snprintf(buf, sizeof(buf), "%s %s %s",
1733                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1734             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1735
1736         } else {
1737             /* Use the rsh program to run telnet program on a gateway host */
1738             if (*appData.remoteUser == NULLCHAR) {
1739                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1740                         appData.gateway, appData.telnetProgram,
1741                         appData.icsHost, appData.icsPort);
1742             } else {
1743                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1744                         appData.remoteShell, appData.gateway,
1745                         appData.remoteUser, appData.telnetProgram,
1746                         appData.icsHost, appData.icsPort);
1747             }
1748             return StartChildProcess(buf, "", &icsPR);
1749
1750         }
1751     } else if (appData.useTelnet) {
1752         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1753
1754     } else {
1755         /* TCP socket interface differs somewhat between
1756            Unix and NT; handle details in the front end.
1757            */
1758         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1759     }
1760 }
1761
1762 void
1763 EscapeExpand (char *p, char *q)
1764 {       // [HGM] initstring: routine to shape up string arguments
1765         while(*p++ = *q++) if(p[-1] == '\\')
1766             switch(*q++) {
1767                 case 'n': p[-1] = '\n'; break;
1768                 case 'r': p[-1] = '\r'; break;
1769                 case 't': p[-1] = '\t'; break;
1770                 case '\\': p[-1] = '\\'; break;
1771                 case 0: *p = 0; return;
1772                 default: p[-1] = q[-1]; break;
1773             }
1774 }
1775
1776 void
1777 show_bytes (FILE *fp, char *buf, int count)
1778 {
1779     while (count--) {
1780         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1781             fprintf(fp, "\\%03o", *buf & 0xff);
1782         } else {
1783             putc(*buf, fp);
1784         }
1785         buf++;
1786     }
1787     fflush(fp);
1788 }
1789
1790 /* Returns an errno value */
1791 int
1792 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1793 {
1794     char buf[8192], *p, *q, *buflim;
1795     int left, newcount, outcount;
1796
1797     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1798         *appData.gateway != NULLCHAR) {
1799         if (appData.debugMode) {
1800             fprintf(debugFP, ">ICS: ");
1801             show_bytes(debugFP, message, count);
1802             fprintf(debugFP, "\n");
1803         }
1804         return OutputToProcess(pr, message, count, outError);
1805     }
1806
1807     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1808     p = message;
1809     q = buf;
1810     left = count;
1811     newcount = 0;
1812     while (left) {
1813         if (q >= buflim) {
1814             if (appData.debugMode) {
1815                 fprintf(debugFP, ">ICS: ");
1816                 show_bytes(debugFP, buf, newcount);
1817                 fprintf(debugFP, "\n");
1818             }
1819             outcount = OutputToProcess(pr, buf, newcount, outError);
1820             if (outcount < newcount) return -1; /* to be sure */
1821             q = buf;
1822             newcount = 0;
1823         }
1824         if (*p == '\n') {
1825             *q++ = '\r';
1826             newcount++;
1827         } else if (((unsigned char) *p) == TN_IAC) {
1828             *q++ = (char) TN_IAC;
1829             newcount ++;
1830         }
1831         *q++ = *p++;
1832         newcount++;
1833         left--;
1834     }
1835     if (appData.debugMode) {
1836         fprintf(debugFP, ">ICS: ");
1837         show_bytes(debugFP, buf, newcount);
1838         fprintf(debugFP, "\n");
1839     }
1840     outcount = OutputToProcess(pr, buf, newcount, outError);
1841     if (outcount < newcount) return -1; /* to be sure */
1842     return count;
1843 }
1844
1845 void
1846 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1847 {
1848     int outError, outCount;
1849     static int gotEof = 0;
1850     static FILE *ini;
1851
1852     /* Pass data read from player on to ICS */
1853     if (count > 0) {
1854         gotEof = 0;
1855         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1856         if (outCount < count) {
1857             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1858         }
1859         if(have_sent_ICS_logon == 2) {
1860           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1861             fprintf(ini, "%s", message);
1862             have_sent_ICS_logon = 3;
1863           } else
1864             have_sent_ICS_logon = 1;
1865         } else if(have_sent_ICS_logon == 3) {
1866             fprintf(ini, "%s", message);
1867             fclose(ini);
1868           have_sent_ICS_logon = 1;
1869         }
1870     } else if (count < 0) {
1871         RemoveInputSource(isr);
1872         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1873     } else if (gotEof++ > 0) {
1874         RemoveInputSource(isr);
1875         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1876     }
1877 }
1878
1879 void
1880 KeepAlive ()
1881 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1882     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1883     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1884     SendToICS("date\n");
1885     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1886 }
1887
1888 /* added routine for printf style output to ics */
1889 void
1890 ics_printf (char *format, ...)
1891 {
1892     char buffer[MSG_SIZ];
1893     va_list args;
1894
1895     va_start(args, format);
1896     vsnprintf(buffer, sizeof(buffer), format, args);
1897     buffer[sizeof(buffer)-1] = '\0';
1898     SendToICS(buffer);
1899     va_end(args);
1900 }
1901
1902 void
1903 SendToICS (char *s)
1904 {
1905     int count, outCount, outError;
1906
1907     if (icsPR == NoProc) return;
1908
1909     count = strlen(s);
1910     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1911     if (outCount < count) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 /* This is used for sending logon scripts to the ICS. Sending
1917    without a delay causes problems when using timestamp on ICC
1918    (at least on my machine). */
1919 void
1920 SendToICSDelayed (char *s, long msdelay)
1921 {
1922     int count, outCount, outError;
1923
1924     if (icsPR == NoProc) return;
1925
1926     count = strlen(s);
1927     if (appData.debugMode) {
1928         fprintf(debugFP, ">ICS: ");
1929         show_bytes(debugFP, s, count);
1930         fprintf(debugFP, "\n");
1931     }
1932     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1933                                       msdelay);
1934     if (outCount < count) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939
1940 /* Remove all highlighting escape sequences in s
1941    Also deletes any suffix starting with '('
1942    */
1943 char *
1944 StripHighlightAndTitle (char *s)
1945 {
1946     static char retbuf[MSG_SIZ];
1947     char *p = retbuf;
1948
1949     while (*s != NULLCHAR) {
1950         while (*s == '\033') {
1951             while (*s != NULLCHAR && !isalpha(*s)) s++;
1952             if (*s != NULLCHAR) s++;
1953         }
1954         while (*s != NULLCHAR && *s != '\033') {
1955             if (*s == '(' || *s == '[') {
1956                 *p = NULLCHAR;
1957                 return retbuf;
1958             }
1959             *p++ = *s++;
1960         }
1961     }
1962     *p = NULLCHAR;
1963     return retbuf;
1964 }
1965
1966 /* Remove all highlighting escape sequences in s */
1967 char *
1968 StripHighlight (char *s)
1969 {
1970     static char retbuf[MSG_SIZ];
1971     char *p = retbuf;
1972
1973     while (*s != NULLCHAR) {
1974         while (*s == '\033') {
1975             while (*s != NULLCHAR && !isalpha(*s)) s++;
1976             if (*s != NULLCHAR) s++;
1977         }
1978         while (*s != NULLCHAR && *s != '\033') {
1979             *p++ = *s++;
1980         }
1981     }
1982     *p = NULLCHAR;
1983     return retbuf;
1984 }
1985
1986 char *variantNames[] = VARIANT_NAMES;
1987 char *
1988 VariantName (VariantClass v)
1989 {
1990     return variantNames[v];
1991 }
1992
1993
1994 /* Identify a variant from the strings the chess servers use or the
1995    PGN Variant tag names we use. */
1996 VariantClass
1997 StringToVariant (char *e)
1998 {
1999     char *p;
2000     int wnum = -1;
2001     VariantClass v = VariantNormal;
2002     int i, found = FALSE;
2003     char buf[MSG_SIZ];
2004     int len;
2005
2006     if (!e) return v;
2007
2008     /* [HGM] skip over optional board-size prefixes */
2009     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2010         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2011         while( *e++ != '_');
2012     }
2013
2014     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2015         v = VariantNormal;
2016         found = TRUE;
2017     } else
2018     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2019       if (StrCaseStr(e, variantNames[i])) {
2020         v = (VariantClass) i;
2021         found = TRUE;
2022         break;
2023       }
2024     }
2025
2026     if (!found) {
2027       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2028           || StrCaseStr(e, "wild/fr")
2029           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2030         v = VariantFischeRandom;
2031       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2032                  (i = 1, p = StrCaseStr(e, "w"))) {
2033         p += i;
2034         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2035         if (isdigit(*p)) {
2036           wnum = atoi(p);
2037         } else {
2038           wnum = -1;
2039         }
2040         switch (wnum) {
2041         case 0: /* FICS only, actually */
2042         case 1:
2043           /* Castling legal even if K starts on d-file */
2044           v = VariantWildCastle;
2045           break;
2046         case 2:
2047         case 3:
2048         case 4:
2049           /* Castling illegal even if K & R happen to start in
2050              normal positions. */
2051           v = VariantNoCastle;
2052           break;
2053         case 5:
2054         case 7:
2055         case 8:
2056         case 10:
2057         case 11:
2058         case 12:
2059         case 13:
2060         case 14:
2061         case 15:
2062         case 18:
2063         case 19:
2064           /* Castling legal iff K & R start in normal positions */
2065           v = VariantNormal;
2066           break;
2067         case 6:
2068         case 20:
2069         case 21:
2070           /* Special wilds for position setup; unclear what to do here */
2071           v = VariantLoadable;
2072           break;
2073         case 9:
2074           /* Bizarre ICC game */
2075           v = VariantTwoKings;
2076           break;
2077         case 16:
2078           v = VariantKriegspiel;
2079           break;
2080         case 17:
2081           v = VariantLosers;
2082           break;
2083         case 22:
2084           v = VariantFischeRandom;
2085           break;
2086         case 23:
2087           v = VariantCrazyhouse;
2088           break;
2089         case 24:
2090           v = VariantBughouse;
2091           break;
2092         case 25:
2093           v = Variant3Check;
2094           break;
2095         case 26:
2096           /* Not quite the same as FICS suicide! */
2097           v = VariantGiveaway;
2098           break;
2099         case 27:
2100           v = VariantAtomic;
2101           break;
2102         case 28:
2103           v = VariantShatranj;
2104           break;
2105
2106         /* Temporary names for future ICC types.  The name *will* change in
2107            the next xboard/WinBoard release after ICC defines it. */
2108         case 29:
2109           v = Variant29;
2110           break;
2111         case 30:
2112           v = Variant30;
2113           break;
2114         case 31:
2115           v = Variant31;
2116           break;
2117         case 32:
2118           v = Variant32;
2119           break;
2120         case 33:
2121           v = Variant33;
2122           break;
2123         case 34:
2124           v = Variant34;
2125           break;
2126         case 35:
2127           v = Variant35;
2128           break;
2129         case 36:
2130           v = Variant36;
2131           break;
2132         case 37:
2133           v = VariantShogi;
2134           break;
2135         case 38:
2136           v = VariantXiangqi;
2137           break;
2138         case 39:
2139           v = VariantCourier;
2140           break;
2141         case 40:
2142           v = VariantGothic;
2143           break;
2144         case 41:
2145           v = VariantCapablanca;
2146           break;
2147         case 42:
2148           v = VariantKnightmate;
2149           break;
2150         case 43:
2151           v = VariantFairy;
2152           break;
2153         case 44:
2154           v = VariantCylinder;
2155           break;
2156         case 45:
2157           v = VariantFalcon;
2158           break;
2159         case 46:
2160           v = VariantCapaRandom;
2161           break;
2162         case 47:
2163           v = VariantBerolina;
2164           break;
2165         case 48:
2166           v = VariantJanus;
2167           break;
2168         case 49:
2169           v = VariantSuper;
2170           break;
2171         case 50:
2172           v = VariantGreat;
2173           break;
2174         case -1:
2175           /* Found "wild" or "w" in the string but no number;
2176              must assume it's normal chess. */
2177           v = VariantNormal;
2178           break;
2179         default:
2180           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2181           if( (len >= MSG_SIZ) && appData.debugMode )
2182             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2183
2184           DisplayError(buf, 0);
2185           v = VariantUnknown;
2186           break;
2187         }
2188       }
2189     }
2190     if (appData.debugMode) {
2191       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2192               e, wnum, VariantName(v));
2193     }
2194     return v;
2195 }
2196
2197 static int leftover_start = 0, leftover_len = 0;
2198 char star_match[STAR_MATCH_N][MSG_SIZ];
2199
2200 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2201    advance *index beyond it, and set leftover_start to the new value of
2202    *index; else return FALSE.  If pattern contains the character '*', it
2203    matches any sequence of characters not containing '\r', '\n', or the
2204    character following the '*' (if any), and the matched sequence(s) are
2205    copied into star_match.
2206    */
2207 int
2208 looking_at ( char *buf, int *index, char *pattern)
2209 {
2210     char *bufp = &buf[*index], *patternp = pattern;
2211     int star_count = 0;
2212     char *matchp = star_match[0];
2213
2214     for (;;) {
2215         if (*patternp == NULLCHAR) {
2216             *index = leftover_start = bufp - buf;
2217             *matchp = NULLCHAR;
2218             return TRUE;
2219         }
2220         if (*bufp == NULLCHAR) return FALSE;
2221         if (*patternp == '*') {
2222             if (*bufp == *(patternp + 1)) {
2223                 *matchp = NULLCHAR;
2224                 matchp = star_match[++star_count];
2225                 patternp += 2;
2226                 bufp++;
2227                 continue;
2228             } else if (*bufp == '\n' || *bufp == '\r') {
2229                 patternp++;
2230                 if (*patternp == NULLCHAR)
2231                   continue;
2232                 else
2233                   return FALSE;
2234             } else {
2235                 *matchp++ = *bufp++;
2236                 continue;
2237             }
2238         }
2239         if (*patternp != *bufp) return FALSE;
2240         patternp++;
2241         bufp++;
2242     }
2243 }
2244
2245 void
2246 SendToPlayer (char *data, int length)
2247 {
2248     int error, outCount;
2249     outCount = OutputToProcess(NoProc, data, length, &error);
2250     if (outCount < length) {
2251         DisplayFatalError(_("Error writing to display"), error, 1);
2252     }
2253 }
2254
2255 void
2256 PackHolding (char packed[], char *holding)
2257 {
2258     char *p = holding;
2259     char *q = packed;
2260     int runlength = 0;
2261     int curr = 9999;
2262     do {
2263         if (*p == curr) {
2264             runlength++;
2265         } else {
2266             switch (runlength) {
2267               case 0:
2268                 break;
2269               case 1:
2270                 *q++ = curr;
2271                 break;
2272               case 2:
2273                 *q++ = curr;
2274                 *q++ = curr;
2275                 break;
2276               default:
2277                 sprintf(q, "%d", runlength);
2278                 while (*q) q++;
2279                 *q++ = curr;
2280                 break;
2281             }
2282             runlength = 1;
2283             curr = *p;
2284         }
2285     } while (*p++);
2286     *q = NULLCHAR;
2287 }
2288
2289 /* Telnet protocol requests from the front end */
2290 void
2291 TelnetRequest (unsigned char ddww, unsigned char option)
2292 {
2293     unsigned char msg[3];
2294     int outCount, outError;
2295
2296     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2297
2298     if (appData.debugMode) {
2299         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2300         switch (ddww) {
2301           case TN_DO:
2302             ddwwStr = "DO";
2303             break;
2304           case TN_DONT:
2305             ddwwStr = "DONT";
2306             break;
2307           case TN_WILL:
2308             ddwwStr = "WILL";
2309             break;
2310           case TN_WONT:
2311             ddwwStr = "WONT";
2312             break;
2313           default:
2314             ddwwStr = buf1;
2315             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2316             break;
2317         }
2318         switch (option) {
2319           case TN_ECHO:
2320             optionStr = "ECHO";
2321             break;
2322           default:
2323             optionStr = buf2;
2324             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2325             break;
2326         }
2327         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2328     }
2329     msg[0] = TN_IAC;
2330     msg[1] = ddww;
2331     msg[2] = option;
2332     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2333     if (outCount < 3) {
2334         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2335     }
2336 }
2337
2338 void
2339 DoEcho ()
2340 {
2341     if (!appData.icsActive) return;
2342     TelnetRequest(TN_DO, TN_ECHO);
2343 }
2344
2345 void
2346 DontEcho ()
2347 {
2348     if (!appData.icsActive) return;
2349     TelnetRequest(TN_DONT, TN_ECHO);
2350 }
2351
2352 void
2353 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2354 {
2355     /* put the holdings sent to us by the server on the board holdings area */
2356     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2357     char p;
2358     ChessSquare piece;
2359
2360     if(gameInfo.holdingsWidth < 2)  return;
2361     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2362         return; // prevent overwriting by pre-board holdings
2363
2364     if( (int)lowestPiece >= BlackPawn ) {
2365         holdingsColumn = 0;
2366         countsColumn = 1;
2367         holdingsStartRow = BOARD_HEIGHT-1;
2368         direction = -1;
2369     } else {
2370         holdingsColumn = BOARD_WIDTH-1;
2371         countsColumn = BOARD_WIDTH-2;
2372         holdingsStartRow = 0;
2373         direction = 1;
2374     }
2375
2376     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2377         board[i][holdingsColumn] = EmptySquare;
2378         board[i][countsColumn]   = (ChessSquare) 0;
2379     }
2380     while( (p=*holdings++) != NULLCHAR ) {
2381         piece = CharToPiece( ToUpper(p) );
2382         if(piece == EmptySquare) continue;
2383         /*j = (int) piece - (int) WhitePawn;*/
2384         j = PieceToNumber(piece);
2385         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2386         if(j < 0) continue;               /* should not happen */
2387         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2388         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2389         board[holdingsStartRow+j*direction][countsColumn]++;
2390     }
2391 }
2392
2393
2394 void
2395 VariantSwitch (Board board, VariantClass newVariant)
2396 {
2397    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2398    static Board oldBoard;
2399
2400    startedFromPositionFile = FALSE;
2401    if(gameInfo.variant == newVariant) return;
2402
2403    /* [HGM] This routine is called each time an assignment is made to
2404     * gameInfo.variant during a game, to make sure the board sizes
2405     * are set to match the new variant. If that means adding or deleting
2406     * holdings, we shift the playing board accordingly
2407     * This kludge is needed because in ICS observe mode, we get boards
2408     * of an ongoing game without knowing the variant, and learn about the
2409     * latter only later. This can be because of the move list we requested,
2410     * in which case the game history is refilled from the beginning anyway,
2411     * but also when receiving holdings of a crazyhouse game. In the latter
2412     * case we want to add those holdings to the already received position.
2413     */
2414
2415
2416    if (appData.debugMode) {
2417      fprintf(debugFP, "Switch board from %s to %s\n",
2418              VariantName(gameInfo.variant), VariantName(newVariant));
2419      setbuf(debugFP, NULL);
2420    }
2421    shuffleOpenings = 0;       /* [HGM] shuffle */
2422    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2423    switch(newVariant)
2424      {
2425      case VariantShogi:
2426        newWidth = 9;  newHeight = 9;
2427        gameInfo.holdingsSize = 7;
2428      case VariantBughouse:
2429      case VariantCrazyhouse:
2430        newHoldingsWidth = 2; break;
2431      case VariantGreat:
2432        newWidth = 10;
2433      case VariantSuper:
2434        newHoldingsWidth = 2;
2435        gameInfo.holdingsSize = 8;
2436        break;
2437      case VariantGothic:
2438      case VariantCapablanca:
2439      case VariantCapaRandom:
2440        newWidth = 10;
2441      default:
2442        newHoldingsWidth = gameInfo.holdingsSize = 0;
2443      };
2444
2445    if(newWidth  != gameInfo.boardWidth  ||
2446       newHeight != gameInfo.boardHeight ||
2447       newHoldingsWidth != gameInfo.holdingsWidth ) {
2448
2449      /* shift position to new playing area, if needed */
2450      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2451        for(i=0; i<BOARD_HEIGHT; i++)
2452          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2453            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2454              board[i][j];
2455        for(i=0; i<newHeight; i++) {
2456          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2457          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2458        }
2459      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2460        for(i=0; i<BOARD_HEIGHT; i++)
2461          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2462            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2463              board[i][j];
2464      }
2465      board[HOLDINGS_SET] = 0;
2466      gameInfo.boardWidth  = newWidth;
2467      gameInfo.boardHeight = newHeight;
2468      gameInfo.holdingsWidth = newHoldingsWidth;
2469      gameInfo.variant = newVariant;
2470      InitDrawingSizes(-2, 0);
2471    } else gameInfo.variant = newVariant;
2472    CopyBoard(oldBoard, board);   // remember correctly formatted board
2473      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2474    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2475 }
2476
2477 static int loggedOn = FALSE;
2478
2479 /*-- Game start info cache: --*/
2480 int gs_gamenum;
2481 char gs_kind[MSG_SIZ];
2482 static char player1Name[128] = "";
2483 static char player2Name[128] = "";
2484 static char cont_seq[] = "\n\\   ";
2485 static int player1Rating = -1;
2486 static int player2Rating = -1;
2487 /*----------------------------*/
2488
2489 ColorClass curColor = ColorNormal;
2490 int suppressKibitz = 0;
2491
2492 // [HGM] seekgraph
2493 Boolean soughtPending = FALSE;
2494 Boolean seekGraphUp;
2495 #define MAX_SEEK_ADS 200
2496 #define SQUARE 0x80
2497 char *seekAdList[MAX_SEEK_ADS];
2498 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2499 float tcList[MAX_SEEK_ADS];
2500 char colorList[MAX_SEEK_ADS];
2501 int nrOfSeekAds = 0;
2502 int minRating = 1010, maxRating = 2800;
2503 int hMargin = 10, vMargin = 20, h, w;
2504 extern int squareSize, lineGap;
2505
2506 void
2507 PlotSeekAd (int i)
2508 {
2509         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2510         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2511         if(r < minRating+100 && r >=0 ) r = minRating+100;
2512         if(r > maxRating) r = maxRating;
2513         if(tc < 1.f) tc = 1.f;
2514         if(tc > 95.f) tc = 95.f;
2515         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2516         y = ((double)r - minRating)/(maxRating - minRating)
2517             * (h-vMargin-squareSize/8-1) + vMargin;
2518         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2519         if(strstr(seekAdList[i], " u ")) color = 1;
2520         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2521            !strstr(seekAdList[i], "bullet") &&
2522            !strstr(seekAdList[i], "blitz") &&
2523            !strstr(seekAdList[i], "standard") ) color = 2;
2524         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2525         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2526 }
2527
2528 void
2529 PlotSingleSeekAd (int i)
2530 {
2531         PlotSeekAd(i);
2532 }
2533
2534 void
2535 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2536 {
2537         char buf[MSG_SIZ], *ext = "";
2538         VariantClass v = StringToVariant(type);
2539         if(strstr(type, "wild")) {
2540             ext = type + 4; // append wild number
2541             if(v == VariantFischeRandom) type = "chess960"; else
2542             if(v == VariantLoadable) type = "setup"; else
2543             type = VariantName(v);
2544         }
2545         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2546         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2547             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2548             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2549             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2550             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2551             seekNrList[nrOfSeekAds] = nr;
2552             zList[nrOfSeekAds] = 0;
2553             seekAdList[nrOfSeekAds++] = StrSave(buf);
2554             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2555         }
2556 }
2557
2558 void
2559 EraseSeekDot (int i)
2560 {
2561     int x = xList[i], y = yList[i], d=squareSize/4, k;
2562     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2563     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2564     // now replot every dot that overlapped
2565     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2566         int xx = xList[k], yy = yList[k];
2567         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2568             DrawSeekDot(xx, yy, colorList[k]);
2569     }
2570 }
2571
2572 void
2573 RemoveSeekAd (int nr)
2574 {
2575         int i;
2576         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2577             EraseSeekDot(i);
2578             if(seekAdList[i]) free(seekAdList[i]);
2579             seekAdList[i] = seekAdList[--nrOfSeekAds];
2580             seekNrList[i] = seekNrList[nrOfSeekAds];
2581             ratingList[i] = ratingList[nrOfSeekAds];
2582             colorList[i]  = colorList[nrOfSeekAds];
2583             tcList[i] = tcList[nrOfSeekAds];
2584             xList[i]  = xList[nrOfSeekAds];
2585             yList[i]  = yList[nrOfSeekAds];
2586             zList[i]  = zList[nrOfSeekAds];
2587             seekAdList[nrOfSeekAds] = NULL;
2588             break;
2589         }
2590 }
2591
2592 Boolean
2593 MatchSoughtLine (char *line)
2594 {
2595     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2596     int nr, base, inc, u=0; char dummy;
2597
2598     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2599        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2600        (u=1) &&
2601        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2602         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2603         // match: compact and save the line
2604         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2605         return TRUE;
2606     }
2607     return FALSE;
2608 }
2609
2610 int
2611 DrawSeekGraph ()
2612 {
2613     int i;
2614     if(!seekGraphUp) return FALSE;
2615     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2616     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2617
2618     DrawSeekBackground(0, 0, w, h);
2619     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2620     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2621     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2622         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2623         yy = h-1-yy;
2624         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2625         if(i%500 == 0) {
2626             char buf[MSG_SIZ];
2627             snprintf(buf, MSG_SIZ, "%d", i);
2628             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2629         }
2630     }
2631     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2632     for(i=1; i<100; i+=(i<10?1:5)) {
2633         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2634         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2635         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2636             char buf[MSG_SIZ];
2637             snprintf(buf, MSG_SIZ, "%d", i);
2638             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2639         }
2640     }
2641     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2642     return TRUE;
2643 }
2644
2645 int
2646 SeekGraphClick (ClickType click, int x, int y, int moving)
2647 {
2648     static int lastDown = 0, displayed = 0, lastSecond;
2649     if(y < 0) return FALSE;
2650     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2651         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2652         if(!seekGraphUp) return FALSE;
2653         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2654         DrawPosition(TRUE, NULL);
2655         return TRUE;
2656     }
2657     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2658         if(click == Release || moving) return FALSE;
2659         nrOfSeekAds = 0;
2660         soughtPending = TRUE;
2661         SendToICS(ics_prefix);
2662         SendToICS("sought\n"); // should this be "sought all"?
2663     } else { // issue challenge based on clicked ad
2664         int dist = 10000; int i, closest = 0, second = 0;
2665         for(i=0; i<nrOfSeekAds; i++) {
2666             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2667             if(d < dist) { dist = d; closest = i; }
2668             second += (d - zList[i] < 120); // count in-range ads
2669             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2670         }
2671         if(dist < 120) {
2672             char buf[MSG_SIZ];
2673             second = (second > 1);
2674             if(displayed != closest || second != lastSecond) {
2675                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2676                 lastSecond = second; displayed = closest;
2677             }
2678             if(click == Press) {
2679                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2680                 lastDown = closest;
2681                 return TRUE;
2682             } // on press 'hit', only show info
2683             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2684             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2685             SendToICS(ics_prefix);
2686             SendToICS(buf);
2687             return TRUE; // let incoming board of started game pop down the graph
2688         } else if(click == Release) { // release 'miss' is ignored
2689             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2690             if(moving == 2) { // right up-click
2691                 nrOfSeekAds = 0; // refresh graph
2692                 soughtPending = TRUE;
2693                 SendToICS(ics_prefix);
2694                 SendToICS("sought\n"); // should this be "sought all"?
2695             }
2696             return TRUE;
2697         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2698         // press miss or release hit 'pop down' seek graph
2699         seekGraphUp = FALSE;
2700         DrawPosition(TRUE, NULL);
2701     }
2702     return TRUE;
2703 }
2704
2705 void
2706 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2707 {
2708 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2709 #define STARTED_NONE 0
2710 #define STARTED_MOVES 1
2711 #define STARTED_BOARD 2
2712 #define STARTED_OBSERVE 3
2713 #define STARTED_HOLDINGS 4
2714 #define STARTED_CHATTER 5
2715 #define STARTED_COMMENT 6
2716 #define STARTED_MOVES_NOHIDE 7
2717
2718     static int started = STARTED_NONE;
2719     static char parse[20000];
2720     static int parse_pos = 0;
2721     static char buf[BUF_SIZE + 1];
2722     static int firstTime = TRUE, intfSet = FALSE;
2723     static ColorClass prevColor = ColorNormal;
2724     static int savingComment = FALSE;
2725     static int cmatch = 0; // continuation sequence match
2726     char *bp;
2727     char str[MSG_SIZ];
2728     int i, oldi;
2729     int buf_len;
2730     int next_out;
2731     int tkind;
2732     int backup;    /* [DM] For zippy color lines */
2733     char *p;
2734     char talker[MSG_SIZ]; // [HGM] chat
2735     int channel;
2736
2737     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2738
2739     if (appData.debugMode) {
2740       if (!error) {
2741         fprintf(debugFP, "<ICS: ");
2742         show_bytes(debugFP, data, count);
2743         fprintf(debugFP, "\n");
2744       }
2745     }
2746
2747     if (appData.debugMode) { int f = forwardMostMove;
2748         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2749                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2750                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2751     }
2752     if (count > 0) {
2753         /* If last read ended with a partial line that we couldn't parse,
2754            prepend it to the new read and try again. */
2755         if (leftover_len > 0) {
2756             for (i=0; i<leftover_len; i++)
2757               buf[i] = buf[leftover_start + i];
2758         }
2759
2760     /* copy new characters into the buffer */
2761     bp = buf + leftover_len;
2762     buf_len=leftover_len;
2763     for (i=0; i<count; i++)
2764     {
2765         // ignore these
2766         if (data[i] == '\r')
2767             continue;
2768
2769         // join lines split by ICS?
2770         if (!appData.noJoin)
2771         {
2772             /*
2773                 Joining just consists of finding matches against the
2774                 continuation sequence, and discarding that sequence
2775                 if found instead of copying it.  So, until a match
2776                 fails, there's nothing to do since it might be the
2777                 complete sequence, and thus, something we don't want
2778                 copied.
2779             */
2780             if (data[i] == cont_seq[cmatch])
2781             {
2782                 cmatch++;
2783                 if (cmatch == strlen(cont_seq))
2784                 {
2785                     cmatch = 0; // complete match.  just reset the counter
2786
2787                     /*
2788                         it's possible for the ICS to not include the space
2789                         at the end of the last word, making our [correct]
2790                         join operation fuse two separate words.  the server
2791                         does this when the space occurs at the width setting.
2792                     */
2793                     if (!buf_len || buf[buf_len-1] != ' ')
2794                     {
2795                         *bp++ = ' ';
2796                         buf_len++;
2797                     }
2798                 }
2799                 continue;
2800             }
2801             else if (cmatch)
2802             {
2803                 /*
2804                     match failed, so we have to copy what matched before
2805                     falling through and copying this character.  In reality,
2806                     this will only ever be just the newline character, but
2807                     it doesn't hurt to be precise.
2808                 */
2809                 strncpy(bp, cont_seq, cmatch);
2810                 bp += cmatch;
2811                 buf_len += cmatch;
2812                 cmatch = 0;
2813             }
2814         }
2815
2816         // copy this char
2817         *bp++ = data[i];
2818         buf_len++;
2819     }
2820
2821         buf[buf_len] = NULLCHAR;
2822 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2823         next_out = 0;
2824         leftover_start = 0;
2825
2826         i = 0;
2827         while (i < buf_len) {
2828             /* Deal with part of the TELNET option negotiation
2829                protocol.  We refuse to do anything beyond the
2830                defaults, except that we allow the WILL ECHO option,
2831                which ICS uses to turn off password echoing when we are
2832                directly connected to it.  We reject this option
2833                if localLineEditing mode is on (always on in xboard)
2834                and we are talking to port 23, which might be a real
2835                telnet server that will try to keep WILL ECHO on permanently.
2836              */
2837             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2838                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2839                 unsigned char option;
2840                 oldi = i;
2841                 switch ((unsigned char) buf[++i]) {
2842                   case TN_WILL:
2843                     if (appData.debugMode)
2844                       fprintf(debugFP, "\n<WILL ");
2845                     switch (option = (unsigned char) buf[++i]) {
2846                       case TN_ECHO:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "ECHO ");
2849                         /* Reply only if this is a change, according
2850                            to the protocol rules. */
2851                         if (remoteEchoOption) break;
2852                         if (appData.localLineEditing &&
2853                             atoi(appData.icsPort) == TN_PORT) {
2854                             TelnetRequest(TN_DONT, TN_ECHO);
2855                         } else {
2856                             EchoOff();
2857                             TelnetRequest(TN_DO, TN_ECHO);
2858                             remoteEchoOption = TRUE;
2859                         }
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", option);
2864                         /* Whatever this is, we don't want it. */
2865                         TelnetRequest(TN_DONT, option);
2866                         break;
2867                     }
2868                     break;
2869                   case TN_WONT:
2870                     if (appData.debugMode)
2871                       fprintf(debugFP, "\n<WONT ");
2872                     switch (option = (unsigned char) buf[++i]) {
2873                       case TN_ECHO:
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "ECHO ");
2876                         /* Reply only if this is a change, according
2877                            to the protocol rules. */
2878                         if (!remoteEchoOption) break;
2879                         EchoOn();
2880                         TelnetRequest(TN_DONT, TN_ECHO);
2881                         remoteEchoOption = FALSE;
2882                         break;
2883                       default:
2884                         if (appData.debugMode)
2885                           fprintf(debugFP, "%d ", (unsigned char) option);
2886                         /* Whatever this is, it must already be turned
2887                            off, because we never agree to turn on
2888                            anything non-default, so according to the
2889                            protocol rules, we don't reply. */
2890                         break;
2891                     }
2892                     break;
2893                   case TN_DO:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<DO ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       default:
2898                         /* Whatever this is, we refuse to do it. */
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "%d ", option);
2901                         TelnetRequest(TN_WONT, option);
2902                         break;
2903                     }
2904                     break;
2905                   case TN_DONT:
2906                     if (appData.debugMode)
2907                       fprintf(debugFP, "\n<DONT ");
2908                     switch (option = (unsigned char) buf[++i]) {
2909                       default:
2910                         if (appData.debugMode)
2911                           fprintf(debugFP, "%d ", option);
2912                         /* Whatever this is, we are already not doing
2913                            it, because we never agree to do anything
2914                            non-default, so according to the protocol
2915                            rules, we don't reply. */
2916                         break;
2917                     }
2918                     break;
2919                   case TN_IAC:
2920                     if (appData.debugMode)
2921                       fprintf(debugFP, "\n<IAC ");
2922                     /* Doubled IAC; pass it through */
2923                     i--;
2924                     break;
2925                   default:
2926                     if (appData.debugMode)
2927                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2928                     /* Drop all other telnet commands on the floor */
2929                     break;
2930                 }
2931                 if (oldi > next_out)
2932                   SendToPlayer(&buf[next_out], oldi - next_out);
2933                 if (++i > next_out)
2934                   next_out = i;
2935                 continue;
2936             }
2937
2938             /* OK, this at least will *usually* work */
2939             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2940                 loggedOn = TRUE;
2941             }
2942
2943             if (loggedOn && !intfSet) {
2944                 if (ics_type == ICS_ICC) {
2945                   snprintf(str, MSG_SIZ,
2946                           "/set-quietly interface %s\n/set-quietly style 12\n",
2947                           programVersion);
2948                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2949                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2950                 } else if (ics_type == ICS_CHESSNET) {
2951                   snprintf(str, MSG_SIZ, "/style 12\n");
2952                 } else {
2953                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2954                   strcat(str, programVersion);
2955                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2956                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2957                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2958 #ifdef WIN32
2959                   strcat(str, "$iset nohighlight 1\n");
2960 #endif
2961                   strcat(str, "$iset lock 1\n$style 12\n");
2962                 }
2963                 SendToICS(str);
2964                 NotifyFrontendLogin();
2965                 intfSet = TRUE;
2966             }
2967
2968             if (started == STARTED_COMMENT) {
2969                 /* Accumulate characters in comment */
2970                 parse[parse_pos++] = buf[i];
2971                 if (buf[i] == '\n') {
2972                     parse[parse_pos] = NULLCHAR;
2973                     if(chattingPartner>=0) {
2974                         char mess[MSG_SIZ];
2975                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2976                         OutputChatMessage(chattingPartner, mess);
2977                         chattingPartner = -1;
2978                         next_out = i+1; // [HGM] suppress printing in ICS window
2979                     } else
2980                     if(!suppressKibitz) // [HGM] kibitz
2981                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2982                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2983                         int nrDigit = 0, nrAlph = 0, j;
2984                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2985                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2986                         parse[parse_pos] = NULLCHAR;
2987                         // try to be smart: if it does not look like search info, it should go to
2988                         // ICS interaction window after all, not to engine-output window.
2989                         for(j=0; j<parse_pos; j++) { // count letters and digits
2990                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2991                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2992                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2993                         }
2994                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2995                             int depth=0; float score;
2996                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2997                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2998                                 pvInfoList[forwardMostMove-1].depth = depth;
2999                                 pvInfoList[forwardMostMove-1].score = 100*score;
3000                             }
3001                             OutputKibitz(suppressKibitz, parse);
3002                         } else {
3003                             char tmp[MSG_SIZ];
3004                             if(gameMode == IcsObserving) // restore original ICS messages
3005                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3006                             else
3007                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3008                             SendToPlayer(tmp, strlen(tmp));
3009                         }
3010                         next_out = i+1; // [HGM] suppress printing in ICS window
3011                     }
3012                     started = STARTED_NONE;
3013                 } else {
3014                     /* Don't match patterns against characters in comment */
3015                     i++;
3016                     continue;
3017                 }
3018             }
3019             if (started == STARTED_CHATTER) {
3020                 if (buf[i] != '\n') {
3021                     /* Don't match patterns against characters in chatter */
3022                     i++;
3023                     continue;
3024                 }
3025                 started = STARTED_NONE;
3026                 if(suppressKibitz) next_out = i+1;
3027             }
3028
3029             /* Kludge to deal with rcmd protocol */
3030             if (firstTime && looking_at(buf, &i, "\001*")) {
3031                 DisplayFatalError(&buf[1], 0, 1);
3032                 continue;
3033             } else {
3034                 firstTime = FALSE;
3035             }
3036
3037             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3038                 ics_type = ICS_ICC;
3039                 ics_prefix = "/";
3040                 if (appData.debugMode)
3041                   fprintf(debugFP, "ics_type %d\n", ics_type);
3042                 continue;
3043             }
3044             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3045                 ics_type = ICS_FICS;
3046                 ics_prefix = "$";
3047                 if (appData.debugMode)
3048                   fprintf(debugFP, "ics_type %d\n", ics_type);
3049                 continue;
3050             }
3051             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3052                 ics_type = ICS_CHESSNET;
3053                 ics_prefix = "/";
3054                 if (appData.debugMode)
3055                   fprintf(debugFP, "ics_type %d\n", ics_type);
3056                 continue;
3057             }
3058
3059             if (!loggedOn &&
3060                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3061                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3062                  looking_at(buf, &i, "will be \"*\""))) {
3063               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3064               continue;
3065             }
3066
3067             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3068               char buf[MSG_SIZ];
3069               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3070               DisplayIcsInteractionTitle(buf);
3071               have_set_title = TRUE;
3072             }
3073
3074             /* skip finger notes */
3075             if (started == STARTED_NONE &&
3076                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3077                  (buf[i] == '1' && buf[i+1] == '0')) &&
3078                 buf[i+2] == ':' && buf[i+3] == ' ') {
3079               started = STARTED_CHATTER;
3080               i += 3;
3081               continue;
3082             }
3083
3084             oldi = i;
3085             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3086             if(appData.seekGraph) {
3087                 if(soughtPending && MatchSoughtLine(buf+i)) {
3088                     i = strstr(buf+i, "rated") - buf;
3089                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090                     next_out = leftover_start = i;
3091                     started = STARTED_CHATTER;
3092                     suppressKibitz = TRUE;
3093                     continue;
3094                 }
3095                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3096                         && looking_at(buf, &i, "* ads displayed")) {
3097                     soughtPending = FALSE;
3098                     seekGraphUp = TRUE;
3099                     DrawSeekGraph();
3100                     continue;
3101                 }
3102                 if(appData.autoRefresh) {
3103                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3104                         int s = (ics_type == ICS_ICC); // ICC format differs
3105                         if(seekGraphUp)
3106                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3107                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3108                         looking_at(buf, &i, "*% "); // eat prompt
3109                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3110                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                         next_out = i; // suppress
3112                         continue;
3113                     }
3114                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3115                         char *p = star_match[0];
3116                         while(*p) {
3117                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3118                             while(*p && *p++ != ' '); // next
3119                         }
3120                         looking_at(buf, &i, "*% "); // eat prompt
3121                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3122                         next_out = i;
3123                         continue;
3124                     }
3125                 }
3126             }
3127
3128             /* skip formula vars */
3129             if (started == STARTED_NONE &&
3130                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3131               started = STARTED_CHATTER;
3132               i += 3;
3133               continue;
3134             }
3135
3136             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3137             if (appData.autoKibitz && started == STARTED_NONE &&
3138                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3139                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3140                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3141                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3142                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3143                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3144                         suppressKibitz = TRUE;
3145                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3146                         next_out = i;
3147                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3148                                 && (gameMode == IcsPlayingWhite)) ||
3149                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3150                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3151                             started = STARTED_CHATTER; // own kibitz we simply discard
3152                         else {
3153                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3154                             parse_pos = 0; parse[0] = NULLCHAR;
3155                             savingComment = TRUE;
3156                             suppressKibitz = gameMode != IcsObserving ? 2 :
3157                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3158                         }
3159                         continue;
3160                 } else
3161                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3162                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3163                          && atoi(star_match[0])) {
3164                     // suppress the acknowledgements of our own autoKibitz
3165                     char *p;
3166                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3168                     SendToPlayer(star_match[0], strlen(star_match[0]));
3169                     if(looking_at(buf, &i, "*% ")) // eat prompt
3170                         suppressKibitz = FALSE;
3171                     next_out = i;
3172                     continue;
3173                 }
3174             } // [HGM] kibitz: end of patch
3175
3176             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3177
3178             // [HGM] chat: intercept tells by users for which we have an open chat window
3179             channel = -1;
3180             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3181                                            looking_at(buf, &i, "* whispers:") ||
3182                                            looking_at(buf, &i, "* kibitzes:") ||
3183                                            looking_at(buf, &i, "* shouts:") ||
3184                                            looking_at(buf, &i, "* c-shouts:") ||
3185                                            looking_at(buf, &i, "--> * ") ||
3186                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3187                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3188                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3189                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3190                 int p;
3191                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3192                 chattingPartner = -1;
3193
3194                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3195                 for(p=0; p<MAX_CHAT; p++) {
3196                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3197                     talker[0] = '['; strcat(talker, "] ");
3198                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3199                     chattingPartner = p; break;
3200                     }
3201                 } else
3202                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3203                 for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("kibitzes", chatPartner[p])) {
3205                         talker[0] = '['; strcat(talker, "] ");
3206                         chattingPartner = p; break;
3207                     }
3208                 } else
3209                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3210                 for(p=0; p<MAX_CHAT; p++) {
3211                     if(!strcmp("whispers", chatPartner[p])) {
3212                         talker[0] = '['; strcat(talker, "] ");
3213                         chattingPartner = p; break;
3214                     }
3215                 } else
3216                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3217                   if(buf[i-8] == '-' && buf[i-3] == 't')
3218                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3219                     if(!strcmp("c-shouts", chatPartner[p])) {
3220                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3221                         chattingPartner = p; break;
3222                     }
3223                   }
3224                   if(chattingPartner < 0)
3225                   for(p=0; p<MAX_CHAT; p++) {
3226                     if(!strcmp("shouts", chatPartner[p])) {
3227                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3228                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3229                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3230                         chattingPartner = p; break;
3231                     }
3232                   }
3233                 }
3234                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3235                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3236                     talker[0] = 0; Colorize(ColorTell, FALSE);
3237                     chattingPartner = p; break;
3238                 }
3239                 if(chattingPartner<0) i = oldi; else {
3240                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3241                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3242                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                     started = STARTED_COMMENT;
3244                     parse_pos = 0; parse[0] = NULLCHAR;
3245                     savingComment = 3 + chattingPartner; // counts as TRUE
3246                     suppressKibitz = TRUE;
3247                     continue;
3248                 }
3249             } // [HGM] chat: end of patch
3250
3251           backup = i;
3252             if (appData.zippyTalk || appData.zippyPlay) {
3253                 /* [DM] Backup address for color zippy lines */
3254 #if ZIPPY
3255                if (loggedOn == TRUE)
3256                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3257                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3258 #endif
3259             } // [DM] 'else { ' deleted
3260                 if (
3261                     /* Regular tells and says */
3262                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3263                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3264                     looking_at(buf, &i, "* says: ") ||
3265                     /* Don't color "message" or "messages" output */
3266                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3267                     looking_at(buf, &i, "*. * at *:*: ") ||
3268                     looking_at(buf, &i, "--* (*:*): ") ||
3269                     /* Message notifications (same color as tells) */
3270                     looking_at(buf, &i, "* has left a message ") ||
3271                     looking_at(buf, &i, "* just sent you a message:\n") ||
3272                     /* Whispers and kibitzes */
3273                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3274                     looking_at(buf, &i, "* kibitzes: ") ||
3275                     /* Channel tells */
3276                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3277
3278                   if (tkind == 1 && strchr(star_match[0], ':')) {
3279                       /* Avoid "tells you:" spoofs in channels */
3280                      tkind = 3;
3281                   }
3282                   if (star_match[0][0] == NULLCHAR ||
3283                       strchr(star_match[0], ' ') ||
3284                       (tkind == 3 && strchr(star_match[1], ' '))) {
3285                     /* Reject bogus matches */
3286                     i = oldi;
3287                   } else {
3288                     if (appData.colorize) {
3289                       if (oldi > next_out) {
3290                         SendToPlayer(&buf[next_out], oldi - next_out);
3291                         next_out = oldi;
3292                       }
3293                       switch (tkind) {
3294                       case 1:
3295                         Colorize(ColorTell, FALSE);
3296                         curColor = ColorTell;
3297                         break;
3298                       case 2:
3299                         Colorize(ColorKibitz, FALSE);
3300                         curColor = ColorKibitz;
3301                         break;
3302                       case 3:
3303                         p = strrchr(star_match[1], '(');
3304                         if (p == NULL) {
3305                           p = star_match[1];
3306                         } else {
3307                           p++;
3308                         }
3309                         if (atoi(p) == 1) {
3310                           Colorize(ColorChannel1, FALSE);
3311                           curColor = ColorChannel1;
3312                         } else {
3313                           Colorize(ColorChannel, FALSE);
3314                           curColor = ColorChannel;
3315                         }
3316                         break;
3317                       case 5:
3318                         curColor = ColorNormal;
3319                         break;
3320                       }
3321                     }
3322                     if (started == STARTED_NONE && appData.autoComment &&
3323                         (gameMode == IcsObserving ||
3324                          gameMode == IcsPlayingWhite ||
3325                          gameMode == IcsPlayingBlack)) {
3326                       parse_pos = i - oldi;
3327                       memcpy(parse, &buf[oldi], parse_pos);
3328                       parse[parse_pos] = NULLCHAR;
3329                       started = STARTED_COMMENT;
3330                       savingComment = TRUE;
3331                     } else {
3332                       started = STARTED_CHATTER;
3333                       savingComment = FALSE;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                   }
3338                 }
3339
3340                 if (looking_at(buf, &i, "* s-shouts: ") ||
3341                     looking_at(buf, &i, "* c-shouts: ")) {
3342                     if (appData.colorize) {
3343                         if (oldi > next_out) {
3344                             SendToPlayer(&buf[next_out], oldi - next_out);
3345                             next_out = oldi;
3346                         }
3347                         Colorize(ColorSShout, FALSE);
3348                         curColor = ColorSShout;
3349                     }
3350                     loggedOn = TRUE;
3351                     started = STARTED_CHATTER;
3352                     continue;
3353                 }
3354
3355                 if (looking_at(buf, &i, "--->")) {
3356                     loggedOn = TRUE;
3357                     continue;
3358                 }
3359
3360                 if (looking_at(buf, &i, "* shouts: ") ||
3361                     looking_at(buf, &i, "--> ")) {
3362                     if (appData.colorize) {
3363                         if (oldi > next_out) {
3364                             SendToPlayer(&buf[next_out], oldi - next_out);
3365                             next_out = oldi;
3366                         }
3367                         Colorize(ColorShout, FALSE);
3368                         curColor = ColorShout;
3369                     }
3370                     loggedOn = TRUE;
3371                     started = STARTED_CHATTER;
3372                     continue;
3373                 }
3374
3375                 if (looking_at( buf, &i, "Challenge:")) {
3376                     if (appData.colorize) {
3377                         if (oldi > next_out) {
3378                             SendToPlayer(&buf[next_out], oldi - next_out);
3379                             next_out = oldi;
3380                         }
3381                         Colorize(ColorChallenge, FALSE);
3382                         curColor = ColorChallenge;
3383                     }
3384                     loggedOn = TRUE;
3385                     continue;
3386                 }
3387
3388                 if (looking_at(buf, &i, "* offers you") ||
3389                     looking_at(buf, &i, "* offers to be") ||
3390                     looking_at(buf, &i, "* would like to") ||
3391                     looking_at(buf, &i, "* requests to") ||
3392                     looking_at(buf, &i, "Your opponent offers") ||
3393                     looking_at(buf, &i, "Your opponent requests")) {
3394
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorRequest, FALSE);
3401                         curColor = ColorRequest;
3402                     }
3403                     continue;
3404                 }
3405
3406                 if (looking_at(buf, &i, "* (*) seeking")) {
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorSeek, FALSE);
3413                         curColor = ColorSeek;
3414                     }
3415                     continue;
3416             }
3417
3418           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3419
3420             if (looking_at(buf, &i, "\\   ")) {
3421                 if (prevColor != ColorNormal) {
3422                     if (oldi > next_out) {
3423                         SendToPlayer(&buf[next_out], oldi - next_out);
3424                         next_out = oldi;
3425                     }
3426                     Colorize(prevColor, TRUE);
3427                     curColor = prevColor;
3428                 }
3429                 if (savingComment) {
3430                     parse_pos = i - oldi;
3431                     memcpy(parse, &buf[oldi], parse_pos);
3432                     parse[parse_pos] = NULLCHAR;
3433                     started = STARTED_COMMENT;
3434                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3435                         chattingPartner = savingComment - 3; // kludge to remember the box
3436                 } else {
3437                     started = STARTED_CHATTER;
3438                 }
3439                 continue;
3440             }
3441
3442             if (looking_at(buf, &i, "Black Strength :") ||
3443                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3444                 looking_at(buf, &i, "<10>") ||
3445                 looking_at(buf, &i, "#@#")) {
3446                 /* Wrong board style */
3447                 loggedOn = TRUE;
3448                 SendToICS(ics_prefix);
3449                 SendToICS("set style 12\n");
3450                 SendToICS(ics_prefix);
3451                 SendToICS("refresh\n");
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i, "login:")) {
3456               if (!have_sent_ICS_logon) {
3457                 if(ICSInitScript())
3458                   have_sent_ICS_logon = 1;
3459                 else // no init script was found
3460                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3461               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3462                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3463               }
3464                 continue;
3465             }
3466
3467             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3468                 (looking_at(buf, &i, "\n<12> ") ||
3469                  looking_at(buf, &i, "<12> "))) {
3470                 loggedOn = TRUE;
3471                 if (oldi > next_out) {
3472                     SendToPlayer(&buf[next_out], oldi - next_out);
3473                 }
3474                 next_out = i;
3475                 started = STARTED_BOARD;
3476                 parse_pos = 0;
3477                 continue;
3478             }
3479
3480             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3481                 looking_at(buf, &i, "<b1> ")) {
3482                 if (oldi > next_out) {
3483                     SendToPlayer(&buf[next_out], oldi - next_out);
3484                 }
3485                 next_out = i;
3486                 started = STARTED_HOLDINGS;
3487                 parse_pos = 0;
3488                 continue;
3489             }
3490
3491             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3492                 loggedOn = TRUE;
3493                 /* Header for a move list -- first line */
3494
3495                 switch (ics_getting_history) {
3496                   case H_FALSE:
3497                     switch (gameMode) {
3498                       case IcsIdle:
3499                       case BeginningOfGame:
3500                         /* User typed "moves" or "oldmoves" while we
3501                            were idle.  Pretend we asked for these
3502                            moves and soak them up so user can step
3503                            through them and/or save them.
3504                            */
3505                         Reset(FALSE, TRUE);
3506                         gameMode = IcsObserving;
3507                         ModeHighlight();
3508                         ics_gamenum = -1;
3509                         ics_getting_history = H_GOT_UNREQ_HEADER;
3510                         break;
3511                       case EditGame: /*?*/
3512                       case EditPosition: /*?*/
3513                         /* Should above feature work in these modes too? */
3514                         /* For now it doesn't */
3515                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3516                         break;
3517                       default:
3518                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3519                         break;
3520                     }
3521                     break;
3522                   case H_REQUESTED:
3523                     /* Is this the right one? */
3524                     if (gameInfo.white && gameInfo.black &&
3525                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3526                         strcmp(gameInfo.black, star_match[2]) == 0) {
3527                         /* All is well */
3528                         ics_getting_history = H_GOT_REQ_HEADER;
3529                     }
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                   case H_GOT_UNREQ_HEADER:
3533                   case H_GOT_UNWANTED_HEADER:
3534                   case H_GETTING_MOVES:
3535                     /* Should not happen */
3536                     DisplayError(_("Error gathering move list: two headers"), 0);
3537                     ics_getting_history = H_FALSE;
3538                     break;
3539                 }
3540
3541                 /* Save player ratings into gameInfo if needed */
3542                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3543                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3544                     (gameInfo.whiteRating == -1 ||
3545                      gameInfo.blackRating == -1)) {
3546
3547                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3548                     gameInfo.blackRating = string_to_rating(star_match[3]);
3549                     if (appData.debugMode)
3550                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3551                               gameInfo.whiteRating, gameInfo.blackRating);
3552                 }
3553                 continue;
3554             }
3555
3556             if (looking_at(buf, &i,
3557               "* * match, initial time: * minute*, increment: * second")) {
3558                 /* Header for a move list -- second line */
3559                 /* Initial board will follow if this is a wild game */
3560                 if (gameInfo.event != NULL) free(gameInfo.event);
3561                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3562                 gameInfo.event = StrSave(str);
3563                 /* [HGM] we switched variant. Translate boards if needed. */
3564                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "Move  ")) {
3569                 /* Beginning of a move list */
3570                 switch (ics_getting_history) {
3571                   case H_FALSE:
3572                     /* Normally should not happen */
3573                     /* Maybe user hit reset while we were parsing */
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Happens if we are ignoring a move list that is not
3577                      * the one we just requested.  Common if the user
3578                      * tries to observe two games without turning off
3579                      * getMoveList */
3580                     break;
3581                   case H_GETTING_MOVES:
3582                     /* Should not happen */
3583                     DisplayError(_("Error gathering move list: nested"), 0);
3584                     ics_getting_history = H_FALSE;
3585                     break;
3586                   case H_GOT_REQ_HEADER:
3587                     ics_getting_history = H_GETTING_MOVES;
3588                     started = STARTED_MOVES;
3589                     parse_pos = 0;
3590                     if (oldi > next_out) {
3591                         SendToPlayer(&buf[next_out], oldi - next_out);
3592                     }
3593                     break;
3594                   case H_GOT_UNREQ_HEADER:
3595                     ics_getting_history = H_GETTING_MOVES;
3596                     started = STARTED_MOVES_NOHIDE;
3597                     parse_pos = 0;
3598                     break;
3599                   case H_GOT_UNWANTED_HEADER:
3600                     ics_getting_history = H_FALSE;
3601                     break;
3602                 }
3603                 continue;
3604             }
3605
3606             if (looking_at(buf, &i, "% ") ||
3607                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3608                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3609                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3610                     soughtPending = FALSE;
3611                     seekGraphUp = TRUE;
3612                     DrawSeekGraph();
3613                 }
3614                 if(suppressKibitz) next_out = i;
3615                 savingComment = FALSE;
3616                 suppressKibitz = 0;
3617                 switch (started) {
3618                   case STARTED_MOVES:
3619                   case STARTED_MOVES_NOHIDE:
3620                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3621                     parse[parse_pos + i - oldi] = NULLCHAR;
3622                     ParseGameHistory(parse);
3623 #if ZIPPY
3624                     if (appData.zippyPlay && first.initDone) {
3625                         FeedMovesToProgram(&first, forwardMostMove);
3626                         if (gameMode == IcsPlayingWhite) {
3627                             if (WhiteOnMove(forwardMostMove)) {
3628                                 if (first.sendTime) {
3629                                   if (first.useColors) {
3630                                     SendToProgram("black\n", &first);
3631                                   }
3632                                   SendTimeRemaining(&first, TRUE);
3633                                 }
3634                                 if (first.useColors) {
3635                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3636                                 }
3637                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3638                                 first.maybeThinking = TRUE;
3639                             } else {
3640                                 if (first.usePlayother) {
3641                                   if (first.sendTime) {
3642                                     SendTimeRemaining(&first, TRUE);
3643                                   }
3644                                   SendToProgram("playother\n", &first);
3645                                   firstMove = FALSE;
3646                                 } else {
3647                                   firstMove = TRUE;
3648                                 }
3649                             }
3650                         } else if (gameMode == IcsPlayingBlack) {
3651                             if (!WhiteOnMove(forwardMostMove)) {
3652                                 if (first.sendTime) {
3653                                   if (first.useColors) {
3654                                     SendToProgram("white\n", &first);
3655                                   }
3656                                   SendTimeRemaining(&first, FALSE);
3657                                 }
3658                                 if (first.useColors) {
3659                                   SendToProgram("black\n", &first);
3660                                 }
3661                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3662                                 first.maybeThinking = TRUE;
3663                             } else {
3664                                 if (first.usePlayother) {
3665                                   if (first.sendTime) {
3666                                     SendTimeRemaining(&first, FALSE);
3667                                   }
3668                                   SendToProgram("playother\n", &first);
3669                                   firstMove = FALSE;
3670                                 } else {
3671                                   firstMove = TRUE;
3672                                 }
3673                             }
3674                         }
3675                     }
3676 #endif
3677                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3678                         /* Moves came from oldmoves or moves command
3679                            while we weren't doing anything else.
3680                            */
3681                         currentMove = forwardMostMove;
3682                         ClearHighlights();/*!!could figure this out*/
3683                         flipView = appData.flipView;
3684                         DrawPosition(TRUE, boards[currentMove]);
3685                         DisplayBothClocks();
3686                         snprintf(str, MSG_SIZ, "%s %s %s",
3687                                 gameInfo.white, _("vs."),  gameInfo.black);
3688                         DisplayTitle(str);
3689                         gameMode = IcsIdle;
3690                     } else {
3691                         /* Moves were history of an active game */
3692                         if (gameInfo.resultDetails != NULL) {
3693                             free(gameInfo.resultDetails);
3694                             gameInfo.resultDetails = NULL;
3695                         }
3696                     }
3697                     HistorySet(parseList, backwardMostMove,
3698                                forwardMostMove, currentMove-1);
3699                     DisplayMove(currentMove - 1);
3700                     if (started == STARTED_MOVES) next_out = i;
3701                     started = STARTED_NONE;
3702                     ics_getting_history = H_FALSE;
3703                     break;
3704
3705                   case STARTED_OBSERVE:
3706                     started = STARTED_NONE;
3707                     SendToICS(ics_prefix);
3708                     SendToICS("refresh\n");
3709                     break;
3710
3711                   default:
3712                     break;
3713                 }
3714                 if(bookHit) { // [HGM] book: simulate book reply
3715                     static char bookMove[MSG_SIZ]; // a bit generous?
3716
3717                     programStats.nodes = programStats.depth = programStats.time =
3718                     programStats.score = programStats.got_only_move = 0;
3719                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3720
3721                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3722                     strcat(bookMove, bookHit);
3723                     HandleMachineMove(bookMove, &first);
3724                 }
3725                 continue;
3726             }
3727
3728             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3729                  started == STARTED_HOLDINGS ||
3730                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3731                 /* Accumulate characters in move list or board */
3732                 parse[parse_pos++] = buf[i];
3733             }
3734
3735             /* Start of game messages.  Mostly we detect start of game
3736                when the first board image arrives.  On some versions
3737                of the ICS, though, we need to do a "refresh" after starting
3738                to observe in order to get the current board right away. */
3739             if (looking_at(buf, &i, "Adding game * to observation list")) {
3740                 started = STARTED_OBSERVE;
3741                 continue;
3742             }
3743
3744             /* Handle auto-observe */
3745             if (appData.autoObserve &&
3746                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3747                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3748                 char *player;
3749                 /* Choose the player that was highlighted, if any. */
3750                 if (star_match[0][0] == '\033' ||
3751                     star_match[1][0] != '\033') {
3752                     player = star_match[0];
3753                 } else {
3754                     player = star_match[2];
3755                 }
3756                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3757                         ics_prefix, StripHighlightAndTitle(player));
3758                 SendToICS(str);
3759
3760                 /* Save ratings from notify string */
3761                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3762                 player1Rating = string_to_rating(star_match[1]);
3763                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3764                 player2Rating = string_to_rating(star_match[3]);
3765
3766                 if (appData.debugMode)
3767                   fprintf(debugFP,
3768                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3769                           player1Name, player1Rating,
3770                           player2Name, player2Rating);
3771
3772                 continue;
3773             }
3774
3775             /* Deal with automatic examine mode after a game,
3776                and with IcsObserving -> IcsExamining transition */
3777             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3778                 looking_at(buf, &i, "has made you an examiner of game *")) {
3779
3780                 int gamenum = atoi(star_match[0]);
3781                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3782                     gamenum == ics_gamenum) {
3783                     /* We were already playing or observing this game;
3784                        no need to refetch history */
3785                     gameMode = IcsExamining;
3786                     if (pausing) {
3787                         pauseExamForwardMostMove = forwardMostMove;
3788                     } else if (currentMove < forwardMostMove) {
3789                         ForwardInner(forwardMostMove);
3790                     }
3791                 } else {
3792                     /* I don't think this case really can happen */
3793                     SendToICS(ics_prefix);
3794                     SendToICS("refresh\n");
3795                 }
3796                 continue;
3797             }
3798
3799             /* Error messages */
3800 //          if (ics_user_moved) {
3801             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3802                 if (looking_at(buf, &i, "Illegal move") ||
3803                     looking_at(buf, &i, "Not a legal move") ||
3804                     looking_at(buf, &i, "Your king is in check") ||
3805                     looking_at(buf, &i, "It isn't your turn") ||
3806                     looking_at(buf, &i, "It is not your move")) {
3807                     /* Illegal move */
3808                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3809                         currentMove = forwardMostMove-1;
3810                         DisplayMove(currentMove - 1); /* before DMError */
3811                         DrawPosition(FALSE, boards[currentMove]);
3812                         SwitchClocks(forwardMostMove-1); // [HGM] race
3813                         DisplayBothClocks();
3814                     }
3815                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3816                     ics_user_moved = 0;
3817                     continue;
3818                 }
3819             }
3820
3821             if (looking_at(buf, &i, "still have time") ||
3822                 looking_at(buf, &i, "not out of time") ||
3823                 looking_at(buf, &i, "either player is out of time") ||
3824                 looking_at(buf, &i, "has timeseal; checking")) {
3825                 /* We must have called his flag a little too soon */
3826                 whiteFlag = blackFlag = FALSE;
3827                 continue;
3828             }
3829
3830             if (looking_at(buf, &i, "added * seconds to") ||
3831                 looking_at(buf, &i, "seconds were added to")) {
3832                 /* Update the clocks */
3833                 SendToICS(ics_prefix);
3834                 SendToICS("refresh\n");
3835                 continue;
3836             }
3837
3838             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3839                 ics_clock_paused = TRUE;
3840                 StopClocks();
3841                 continue;
3842             }
3843
3844             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3845                 ics_clock_paused = FALSE;
3846                 StartClocks();
3847                 continue;
3848             }
3849
3850             /* Grab player ratings from the Creating: message.
3851                Note we have to check for the special case when
3852                the ICS inserts things like [white] or [black]. */
3853             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3854                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3855                 /* star_matches:
3856                    0    player 1 name (not necessarily white)
3857                    1    player 1 rating
3858                    2    empty, white, or black (IGNORED)
3859                    3    player 2 name (not necessarily black)
3860                    4    player 2 rating
3861
3862                    The names/ratings are sorted out when the game
3863                    actually starts (below).
3864                 */
3865                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3866                 player1Rating = string_to_rating(star_match[1]);
3867                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3868                 player2Rating = string_to_rating(star_match[4]);
3869
3870                 if (appData.debugMode)
3871                   fprintf(debugFP,
3872                           "Ratings from 'Creating:' %s %d, %s %d\n",
3873                           player1Name, player1Rating,
3874                           player2Name, player2Rating);
3875
3876                 continue;
3877             }
3878
3879             /* Improved generic start/end-of-game messages */
3880             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3881                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3882                 /* If tkind == 0: */
3883                 /* star_match[0] is the game number */
3884                 /*           [1] is the white player's name */
3885                 /*           [2] is the black player's name */
3886                 /* For end-of-game: */
3887                 /*           [3] is the reason for the game end */
3888                 /*           [4] is a PGN end game-token, preceded by " " */
3889                 /* For start-of-game: */
3890                 /*           [3] begins with "Creating" or "Continuing" */
3891                 /*           [4] is " *" or empty (don't care). */
3892                 int gamenum = atoi(star_match[0]);
3893                 char *whitename, *blackname, *why, *endtoken;
3894                 ChessMove endtype = EndOfFile;
3895
3896                 if (tkind == 0) {
3897                   whitename = star_match[1];
3898                   blackname = star_match[2];
3899                   why = star_match[3];
3900                   endtoken = star_match[4];
3901                 } else {
3902                   whitename = star_match[1];
3903                   blackname = star_match[3];
3904                   why = star_match[5];
3905                   endtoken = star_match[6];
3906                 }
3907
3908                 /* Game start messages */
3909                 if (strncmp(why, "Creating ", 9) == 0 ||
3910                     strncmp(why, "Continuing ", 11) == 0) {
3911                     gs_gamenum = gamenum;
3912                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3913                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3914                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3915 #if ZIPPY
3916                     if (appData.zippyPlay) {
3917                         ZippyGameStart(whitename, blackname);
3918                     }
3919 #endif /*ZIPPY*/
3920                     partnerBoardValid = FALSE; // [HGM] bughouse
3921                     continue;
3922                 }
3923
3924                 /* Game end messages */
3925                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3926                     ics_gamenum != gamenum) {
3927                     continue;
3928                 }
3929                 while (endtoken[0] == ' ') endtoken++;
3930                 switch (endtoken[0]) {
3931                   case '*':
3932                   default:
3933                     endtype = GameUnfinished;
3934                     break;
3935                   case '0':
3936                     endtype = BlackWins;
3937                     break;
3938                   case '1':
3939                     if (endtoken[1] == '/')
3940                       endtype = GameIsDrawn;
3941                     else
3942                       endtype = WhiteWins;
3943                     break;
3944                 }
3945                 GameEnds(endtype, why, GE_ICS);
3946 #if ZIPPY
3947                 if (appData.zippyPlay && first.initDone) {
3948                     ZippyGameEnd(endtype, why);
3949                     if (first.pr == NoProc) {
3950                       /* Start the next process early so that we'll
3951                          be ready for the next challenge */
3952                       StartChessProgram(&first);
3953                     }
3954                     /* Send "new" early, in case this command takes
3955                        a long time to finish, so that we'll be ready
3956                        for the next challenge. */
3957                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3958                     Reset(TRUE, TRUE);
3959                 }
3960 #endif /*ZIPPY*/
3961                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "Removing game * from observation") ||
3966                 looking_at(buf, &i, "no longer observing game *") ||
3967                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3968                 if (gameMode == IcsObserving &&
3969                     atoi(star_match[0]) == ics_gamenum)
3970                   {
3971                       /* icsEngineAnalyze */
3972                       if (appData.icsEngineAnalyze) {
3973                             ExitAnalyzeMode();
3974                             ModeHighlight();
3975                       }
3976                       StopClocks();
3977                       gameMode = IcsIdle;
3978                       ics_gamenum = -1;
3979                       ics_user_moved = FALSE;
3980                   }
3981                 continue;
3982             }
3983
3984             if (looking_at(buf, &i, "no longer examining game *")) {
3985                 if (gameMode == IcsExamining &&
3986                     atoi(star_match[0]) == ics_gamenum)
3987                   {
3988                       gameMode = IcsIdle;
3989                       ics_gamenum = -1;
3990                       ics_user_moved = FALSE;
3991                   }
3992                 continue;
3993             }
3994
3995             /* Advance leftover_start past any newlines we find,
3996                so only partial lines can get reparsed */
3997             if (looking_at(buf, &i, "\n")) {
3998                 prevColor = curColor;
3999                 if (curColor != ColorNormal) {
4000                     if (oldi > next_out) {
4001                         SendToPlayer(&buf[next_out], oldi - next_out);
4002                         next_out = oldi;
4003                     }
4004                     Colorize(ColorNormal, FALSE);
4005                     curColor = ColorNormal;
4006                 }
4007                 if (started == STARTED_BOARD) {
4008                     started = STARTED_NONE;
4009                     parse[parse_pos] = NULLCHAR;
4010                     ParseBoard12(parse);
4011                     ics_user_moved = 0;
4012
4013                     /* Send premove here */
4014                     if (appData.premove) {
4015                       char str[MSG_SIZ];
4016                       if (currentMove == 0 &&
4017                           gameMode == IcsPlayingWhite &&
4018                           appData.premoveWhite) {
4019                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4020                         if (appData.debugMode)
4021                           fprintf(debugFP, "Sending premove:\n");
4022                         SendToICS(str);
4023                       } else if (currentMove == 1 &&
4024                                  gameMode == IcsPlayingBlack &&
4025                                  appData.premoveBlack) {
4026                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4027                         if (appData.debugMode)
4028                           fprintf(debugFP, "Sending premove:\n");
4029                         SendToICS(str);
4030                       } else if (gotPremove) {
4031                         gotPremove = 0;
4032                         ClearPremoveHighlights();
4033                         if (appData.debugMode)
4034                           fprintf(debugFP, "Sending premove:\n");
4035                           UserMoveEvent(premoveFromX, premoveFromY,
4036                                         premoveToX, premoveToY,
4037                                         premovePromoChar);
4038                       }
4039                     }
4040
4041                     /* Usually suppress following prompt */
4042                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4043                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4044                         if (looking_at(buf, &i, "*% ")) {
4045                             savingComment = FALSE;
4046                             suppressKibitz = 0;
4047                         }
4048                     }
4049                     next_out = i;
4050                 } else if (started == STARTED_HOLDINGS) {
4051                     int gamenum;
4052                     char new_piece[MSG_SIZ];
4053                     started = STARTED_NONE;
4054                     parse[parse_pos] = NULLCHAR;
4055                     if (appData.debugMode)
4056                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4057                                                         parse, currentMove);
4058                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4059                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4060                         if (gameInfo.variant == VariantNormal) {
4061                           /* [HGM] We seem to switch variant during a game!
4062                            * Presumably no holdings were displayed, so we have
4063                            * to move the position two files to the right to
4064                            * create room for them!
4065                            */
4066                           VariantClass newVariant;
4067                           switch(gameInfo.boardWidth) { // base guess on board width
4068                                 case 9:  newVariant = VariantShogi; break;
4069                                 case 10: newVariant = VariantGreat; break;
4070                                 default: newVariant = VariantCrazyhouse; break;
4071                           }
4072                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4073                           /* Get a move list just to see the header, which
4074                              will tell us whether this is really bug or zh */
4075                           if (ics_getting_history == H_FALSE) {
4076                             ics_getting_history = H_REQUESTED;
4077                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4078                             SendToICS(str);
4079                           }
4080                         }
4081                         new_piece[0] = NULLCHAR;
4082                         sscanf(parse, "game %d white [%s black [%s <- %s",
4083                                &gamenum, white_holding, black_holding,
4084                                new_piece);
4085                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4086                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4087                         /* [HGM] copy holdings to board holdings area */
4088                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4089                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4090                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4091 #if ZIPPY
4092                         if (appData.zippyPlay && first.initDone) {
4093                             ZippyHoldings(white_holding, black_holding,
4094                                           new_piece);
4095                         }
4096 #endif /*ZIPPY*/
4097                         if (tinyLayout || smallLayout) {
4098                             char wh[16], bh[16];
4099                             PackHolding(wh, white_holding);
4100                             PackHolding(bh, black_holding);
4101                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4102                                     gameInfo.white, gameInfo.black);
4103                         } else {
4104                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4105                                     gameInfo.white, white_holding, _("vs."),
4106                                     gameInfo.black, black_holding);
4107                         }
4108                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4109                         DrawPosition(FALSE, boards[currentMove]);
4110                         DisplayTitle(str);
4111                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4112                         sscanf(parse, "game %d white [%s black [%s <- %s",
4113                                &gamenum, white_holding, black_holding,
4114                                new_piece);
4115                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4116                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4117                         /* [HGM] copy holdings to partner-board holdings area */
4118                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4119                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4120                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4121                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4122                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4123                       }
4124                     }
4125                     /* Suppress following prompt */
4126                     if (looking_at(buf, &i, "*% ")) {
4127                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4128                         savingComment = FALSE;
4129                         suppressKibitz = 0;
4130                     }
4131                     next_out = i;
4132                 }
4133                 continue;
4134             }
4135
4136             i++;                /* skip unparsed character and loop back */
4137         }
4138
4139         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4140 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4141 //          SendToPlayer(&buf[next_out], i - next_out);
4142             started != STARTED_HOLDINGS && leftover_start > next_out) {
4143             SendToPlayer(&buf[next_out], leftover_start - next_out);
4144             next_out = i;
4145         }
4146
4147         leftover_len = buf_len - leftover_start;
4148         /* if buffer ends with something we couldn't parse,
4149            reparse it after appending the next read */
4150
4151     } else if (count == 0) {
4152         RemoveInputSource(isr);
4153         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4154     } else {
4155         DisplayFatalError(_("Error reading from ICS"), error, 1);
4156     }
4157 }
4158
4159
4160 /* Board style 12 looks like this:
4161
4162    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4163
4164  * The "<12> " is stripped before it gets to this routine.  The two
4165  * trailing 0's (flip state and clock ticking) are later addition, and
4166  * some chess servers may not have them, or may have only the first.
4167  * Additional trailing fields may be added in the future.
4168  */
4169
4170 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4171
4172 #define RELATION_OBSERVING_PLAYED    0
4173 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4174 #define RELATION_PLAYING_MYMOVE      1
4175 #define RELATION_PLAYING_NOTMYMOVE  -1
4176 #define RELATION_EXAMINING           2
4177 #define RELATION_ISOLATED_BOARD     -3
4178 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4179
4180 void
4181 ParseBoard12 (char *string)
4182 {
4183 #if ZIPPY
4184     int i, takeback;
4185     char *bookHit = NULL; // [HGM] book
4186 #endif
4187     GameMode newGameMode;
4188     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4189     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4190     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4191     char to_play, board_chars[200];
4192     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4193     char black[32], white[32];
4194     Board board;
4195     int prevMove = currentMove;
4196     int ticking = 2;
4197     ChessMove moveType;
4198     int fromX, fromY, toX, toY;
4199     char promoChar;
4200     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4201     Boolean weird = FALSE, reqFlag = FALSE;
4202
4203     fromX = fromY = toX = toY = -1;
4204
4205     newGame = FALSE;
4206
4207     if (appData.debugMode)
4208       fprintf(debugFP, _("Parsing board: %s\n"), string);
4209
4210     move_str[0] = NULLCHAR;
4211     elapsed_time[0] = NULLCHAR;
4212     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4213         int  i = 0, j;
4214         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4215             if(string[i] == ' ') { ranks++; files = 0; }
4216             else files++;
4217             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4218             i++;
4219         }
4220         for(j = 0; j <i; j++) board_chars[j] = string[j];
4221         board_chars[i] = '\0';
4222         string += i + 1;
4223     }
4224     n = sscanf(string, PATTERN, &to_play, &double_push,
4225                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4226                &gamenum, white, black, &relation, &basetime, &increment,
4227                &white_stren, &black_stren, &white_time, &black_time,
4228                &moveNum, str, elapsed_time, move_str, &ics_flip,
4229                &ticking);
4230
4231     if (n < 21) {
4232         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4233         DisplayError(str, 0);
4234         return;
4235     }
4236
4237     /* Convert the move number to internal form */
4238     moveNum = (moveNum - 1) * 2;
4239     if (to_play == 'B') moveNum++;
4240     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4241       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4242                         0, 1);
4243       return;
4244     }
4245
4246     switch (relation) {
4247       case RELATION_OBSERVING_PLAYED:
4248       case RELATION_OBSERVING_STATIC:
4249         if (gamenum == -1) {
4250             /* Old ICC buglet */
4251             relation = RELATION_OBSERVING_STATIC;
4252         }
4253         newGameMode = IcsObserving;
4254         break;
4255       case RELATION_PLAYING_MYMOVE:
4256       case RELATION_PLAYING_NOTMYMOVE:
4257         newGameMode =
4258           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4259             IcsPlayingWhite : IcsPlayingBlack;
4260         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4261         break;
4262       case RELATION_EXAMINING:
4263         newGameMode = IcsExamining;
4264         break;
4265       case RELATION_ISOLATED_BOARD:
4266       default:
4267         /* Just display this board.  If user was doing something else,
4268            we will forget about it until the next board comes. */
4269         newGameMode = IcsIdle;
4270         break;
4271       case RELATION_STARTING_POSITION:
4272         newGameMode = gameMode;
4273         break;
4274     }
4275
4276     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4277         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4278          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4279       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4280       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4281       static int lastBgGame = -1;
4282       char *toSqr;
4283       for (k = 0; k < ranks; k++) {
4284         for (j = 0; j < files; j++)
4285           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4286         if(gameInfo.holdingsWidth > 1) {
4287              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4288              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4289         }
4290       }
4291       CopyBoard(partnerBoard, board);
4292       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4293         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4294         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4295       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4296       if(toSqr = strchr(str, '-')) {
4297         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4298         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4299       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4300       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4301       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4302       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4303       if(twoBoards) {
4304           DisplayWhiteClock(white_time*fac, to_play == 'W');
4305           DisplayBlackClock(black_time*fac, to_play != 'W');
4306           activePartner = to_play;
4307           if(gamenum != lastBgGame) {
4308               char buf[MSG_SIZ];
4309               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4310               DisplayTitle(buf);
4311           }
4312           lastBgGame = gamenum;
4313           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4314                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4315       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4316                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4317       DisplayMessage(partnerStatus, "");
4318         partnerBoardValid = TRUE;
4319       return;
4320     }
4321
4322     if(appData.dualBoard && appData.bgObserve) {
4323         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4324             SendToICS(ics_prefix), SendToICS("pobserve\n");
4325         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4326             char buf[MSG_SIZ];
4327             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4328             SendToICS(buf);
4329         }
4330     }
4331
4332     /* Modify behavior for initial board display on move listing
4333        of wild games.
4334        */
4335     switch (ics_getting_history) {
4336       case H_FALSE:
4337       case H_REQUESTED:
4338         break;
4339       case H_GOT_REQ_HEADER:
4340       case H_GOT_UNREQ_HEADER:
4341         /* This is the initial position of the current game */
4342         gamenum = ics_gamenum;
4343         moveNum = 0;            /* old ICS bug workaround */
4344         if (to_play == 'B') {
4345           startedFromSetupPosition = TRUE;
4346           blackPlaysFirst = TRUE;
4347           moveNum = 1;
4348           if (forwardMostMove == 0) forwardMostMove = 1;
4349           if (backwardMostMove == 0) backwardMostMove = 1;
4350           if (currentMove == 0) currentMove = 1;
4351         }
4352         newGameMode = gameMode;
4353         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4354         break;
4355       case H_GOT_UNWANTED_HEADER:
4356         /* This is an initial board that we don't want */
4357         return;
4358       case H_GETTING_MOVES:
4359         /* Should not happen */
4360         DisplayError(_("Error gathering move list: extra board"), 0);
4361         ics_getting_history = H_FALSE;
4362         return;
4363     }
4364
4365    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4366                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4367                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4368      /* [HGM] We seem to have switched variant unexpectedly
4369       * Try to guess new variant from board size
4370       */
4371           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4372           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4373           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4374           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4375           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4376           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4377           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4378           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4379           /* Get a move list just to see the header, which
4380              will tell us whether this is really bug or zh */
4381           if (ics_getting_history == H_FALSE) {
4382             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4383             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4384             SendToICS(str);
4385           }
4386     }
4387
4388     /* Take action if this is the first board of a new game, or of a
4389        different game than is currently being displayed.  */
4390     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4391         relation == RELATION_ISOLATED_BOARD) {
4392
4393         /* Forget the old game and get the history (if any) of the new one */
4394         if (gameMode != BeginningOfGame) {
4395           Reset(TRUE, TRUE);
4396         }
4397         newGame = TRUE;
4398         if (appData.autoRaiseBoard) BoardToTop();
4399         prevMove = -3;
4400         if (gamenum == -1) {
4401             newGameMode = IcsIdle;
4402         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4403                    appData.getMoveList && !reqFlag) {
4404             /* Need to get game history */
4405             ics_getting_history = H_REQUESTED;
4406             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4407             SendToICS(str);
4408         }
4409
4410         /* Initially flip the board to have black on the bottom if playing
4411            black or if the ICS flip flag is set, but let the user change
4412            it with the Flip View button. */
4413         flipView = appData.autoFlipView ?
4414           (newGameMode == IcsPlayingBlack) || ics_flip :
4415           appData.flipView;
4416
4417         /* Done with values from previous mode; copy in new ones */
4418         gameMode = newGameMode;
4419         ModeHighlight();
4420         ics_gamenum = gamenum;
4421         if (gamenum == gs_gamenum) {
4422             int klen = strlen(gs_kind);
4423             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4424             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4425             gameInfo.event = StrSave(str);
4426         } else {
4427             gameInfo.event = StrSave("ICS game");
4428         }
4429         gameInfo.site = StrSave(appData.icsHost);
4430         gameInfo.date = PGNDate();
4431         gameInfo.round = StrSave("-");
4432         gameInfo.white = StrSave(white);
4433         gameInfo.black = StrSave(black);
4434         timeControl = basetime * 60 * 1000;
4435         timeControl_2 = 0;
4436         timeIncrement = increment * 1000;
4437         movesPerSession = 0;
4438         gameInfo.timeControl = TimeControlTagValue();
4439         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4440   if (appData.debugMode) {
4441     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4442     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4443     setbuf(debugFP, NULL);
4444   }
4445
4446         gameInfo.outOfBook = NULL;
4447
4448         /* Do we have the ratings? */
4449         if (strcmp(player1Name, white) == 0 &&
4450             strcmp(player2Name, black) == 0) {
4451             if (appData.debugMode)
4452               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4453                       player1Rating, player2Rating);
4454             gameInfo.whiteRating = player1Rating;
4455             gameInfo.blackRating = player2Rating;
4456         } else if (strcmp(player2Name, white) == 0 &&
4457                    strcmp(player1Name, black) == 0) {
4458             if (appData.debugMode)
4459               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4460                       player2Rating, player1Rating);
4461             gameInfo.whiteRating = player2Rating;
4462             gameInfo.blackRating = player1Rating;
4463         }
4464         player1Name[0] = player2Name[0] = NULLCHAR;
4465
4466         /* Silence shouts if requested */
4467         if (appData.quietPlay &&
4468             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4469             SendToICS(ics_prefix);
4470             SendToICS("set shout 0\n");
4471         }
4472     }
4473
4474     /* Deal with midgame name changes */
4475     if (!newGame) {
4476         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4477             if (gameInfo.white) free(gameInfo.white);
4478             gameInfo.white = StrSave(white);
4479         }
4480         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4481             if (gameInfo.black) free(gameInfo.black);
4482             gameInfo.black = StrSave(black);
4483         }
4484     }
4485
4486     /* Throw away game result if anything actually changes in examine mode */
4487     if (gameMode == IcsExamining && !newGame) {
4488         gameInfo.result = GameUnfinished;
4489         if (gameInfo.resultDetails != NULL) {
4490             free(gameInfo.resultDetails);
4491             gameInfo.resultDetails = NULL;
4492         }
4493     }
4494
4495     /* In pausing && IcsExamining mode, we ignore boards coming
4496        in if they are in a different variation than we are. */
4497     if (pauseExamInvalid) return;
4498     if (pausing && gameMode == IcsExamining) {
4499         if (moveNum <= pauseExamForwardMostMove) {
4500             pauseExamInvalid = TRUE;
4501             forwardMostMove = pauseExamForwardMostMove;
4502             return;
4503         }
4504     }
4505
4506   if (appData.debugMode) {
4507     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4508   }
4509     /* Parse the board */
4510     for (k = 0; k < ranks; k++) {
4511       for (j = 0; j < files; j++)
4512         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4513       if(gameInfo.holdingsWidth > 1) {
4514            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4515            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4516       }
4517     }
4518     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4519       board[5][BOARD_RGHT+1] = WhiteAngel;
4520       board[6][BOARD_RGHT+1] = WhiteMarshall;
4521       board[1][0] = BlackMarshall;
4522       board[2][0] = BlackAngel;
4523       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4524     }
4525     CopyBoard(boards[moveNum], board);
4526     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4527     if (moveNum == 0) {
4528         startedFromSetupPosition =
4529           !CompareBoards(board, initialPosition);
4530         if(startedFromSetupPosition)
4531             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4532     }
4533
4534     /* [HGM] Set castling rights. Take the outermost Rooks,
4535        to make it also work for FRC opening positions. Note that board12
4536        is really defective for later FRC positions, as it has no way to
4537        indicate which Rook can castle if they are on the same side of King.
4538        For the initial position we grant rights to the outermost Rooks,
4539        and remember thos rights, and we then copy them on positions
4540        later in an FRC game. This means WB might not recognize castlings with
4541        Rooks that have moved back to their original position as illegal,
4542        but in ICS mode that is not its job anyway.
4543     */
4544     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4545     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4546
4547         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4548             if(board[0][i] == WhiteRook) j = i;
4549         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4550         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4551             if(board[0][i] == WhiteRook) j = i;
4552         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4553         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4554             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4555         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4556         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4557             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4558         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4559
4560         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4561         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4562         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4563             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4564         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4565             if(board[BOARD_HEIGHT-1][k] == bKing)
4566                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4567         if(gameInfo.variant == VariantTwoKings) {
4568             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4569             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4570             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4571         }
4572     } else { int r;
4573         r = boards[moveNum][CASTLING][0] = initialRights[0];
4574         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4575         r = boards[moveNum][CASTLING][1] = initialRights[1];
4576         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4577         r = boards[moveNum][CASTLING][3] = initialRights[3];
4578         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4579         r = boards[moveNum][CASTLING][4] = initialRights[4];
4580         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4581         /* wildcastle kludge: always assume King has rights */
4582         r = boards[moveNum][CASTLING][2] = initialRights[2];
4583         r = boards[moveNum][CASTLING][5] = initialRights[5];
4584     }
4585     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4586     boards[moveNum][EP_STATUS] = EP_NONE;
4587     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4588     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4589     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4590
4591
4592     if (ics_getting_history == H_GOT_REQ_HEADER ||
4593         ics_getting_history == H_GOT_UNREQ_HEADER) {
4594         /* This was an initial position from a move list, not
4595            the current position */
4596         return;
4597     }
4598
4599     /* Update currentMove and known move number limits */
4600     newMove = newGame || moveNum > forwardMostMove;
4601
4602     if (newGame) {
4603         forwardMostMove = backwardMostMove = currentMove = moveNum;
4604         if (gameMode == IcsExamining && moveNum == 0) {
4605           /* Workaround for ICS limitation: we are not told the wild
4606              type when starting to examine a game.  But if we ask for
4607              the move list, the move list header will tell us */
4608             ics_getting_history = H_REQUESTED;
4609             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4610             SendToICS(str);
4611         }
4612     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4613                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4614 #if ZIPPY
4615         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4616         /* [HGM] applied this also to an engine that is silently watching        */
4617         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4618             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4619             gameInfo.variant == currentlyInitializedVariant) {
4620           takeback = forwardMostMove - moveNum;
4621           for (i = 0; i < takeback; i++) {
4622             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4623             SendToProgram("undo\n", &first);
4624           }
4625         }
4626 #endif
4627
4628         forwardMostMove = moveNum;
4629         if (!pausing || currentMove > forwardMostMove)
4630           currentMove = forwardMostMove;
4631     } else {
4632         /* New part of history that is not contiguous with old part */
4633         if (pausing && gameMode == IcsExamining) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4639 #if ZIPPY
4640             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4641                 // [HGM] when we will receive the move list we now request, it will be
4642                 // fed to the engine from the first move on. So if the engine is not
4643                 // in the initial position now, bring it there.
4644                 InitChessProgram(&first, 0);
4645             }
4646 #endif
4647             ics_getting_history = H_REQUESTED;
4648             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4649             SendToICS(str);
4650         }
4651         forwardMostMove = backwardMostMove = currentMove = moveNum;
4652     }
4653
4654     /* Update the clocks */
4655     if (strchr(elapsed_time, '.')) {
4656       /* Time is in ms */
4657       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4658       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4659     } else {
4660       /* Time is in seconds */
4661       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4662       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4663     }
4664
4665
4666 #if ZIPPY
4667     if (appData.zippyPlay && newGame &&
4668         gameMode != IcsObserving && gameMode != IcsIdle &&
4669         gameMode != IcsExamining)
4670       ZippyFirstBoard(moveNum, basetime, increment);
4671 #endif
4672
4673     /* Put the move on the move list, first converting
4674        to canonical algebraic form. */
4675     if (moveNum > 0) {
4676   if (appData.debugMode) {
4677     if (appData.debugMode) { int f = forwardMostMove;
4678         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4679                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4680                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4681     }
4682     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4683     fprintf(debugFP, "moveNum = %d\n", moveNum);
4684     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4685     setbuf(debugFP, NULL);
4686   }
4687         if (moveNum <= backwardMostMove) {
4688             /* We don't know what the board looked like before
4689                this move.  Punt. */
4690           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4691             strcat(parseList[moveNum - 1], " ");
4692             strcat(parseList[moveNum - 1], elapsed_time);
4693             moveList[moveNum - 1][0] = NULLCHAR;
4694         } else if (strcmp(move_str, "none") == 0) {
4695             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4696             /* Again, we don't know what the board looked like;
4697                this is really the start of the game. */
4698             parseList[moveNum - 1][0] = NULLCHAR;
4699             moveList[moveNum - 1][0] = NULLCHAR;
4700             backwardMostMove = moveNum;
4701             startedFromSetupPosition = TRUE;
4702             fromX = fromY = toX = toY = -1;
4703         } else {
4704           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4705           //                 So we parse the long-algebraic move string in stead of the SAN move
4706           int valid; char buf[MSG_SIZ], *prom;
4707
4708           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4709                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4710           // str looks something like "Q/a1-a2"; kill the slash
4711           if(str[1] == '/')
4712             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4713           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4714           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4715                 strcat(buf, prom); // long move lacks promo specification!
4716           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4717                 if(appData.debugMode)
4718                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4719                 safeStrCpy(move_str, buf, MSG_SIZ);
4720           }
4721           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4722                                 &fromX, &fromY, &toX, &toY, &promoChar)
4723                || ParseOneMove(buf, moveNum - 1, &moveType,
4724                                 &fromX, &fromY, &toX, &toY, &promoChar);
4725           // end of long SAN patch
4726           if (valid) {
4727             (void) CoordsToAlgebraic(boards[moveNum - 1],
4728                                      PosFlags(moveNum - 1),
4729                                      fromY, fromX, toY, toX, promoChar,
4730                                      parseList[moveNum-1]);
4731             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4732               case MT_NONE:
4733               case MT_STALEMATE:
4734               default:
4735                 break;
4736               case MT_CHECK:
4737                 if(gameInfo.variant != VariantShogi)
4738                     strcat(parseList[moveNum - 1], "+");
4739                 break;
4740               case MT_CHECKMATE:
4741               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4742                 strcat(parseList[moveNum - 1], "#");
4743                 break;
4744             }
4745             strcat(parseList[moveNum - 1], " ");
4746             strcat(parseList[moveNum - 1], elapsed_time);
4747             /* currentMoveString is set as a side-effect of ParseOneMove */
4748             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4749             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4750             strcat(moveList[moveNum - 1], "\n");
4751
4752             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4753                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4754               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4755                 ChessSquare old, new = boards[moveNum][k][j];
4756                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4757                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4758                   if(old == new) continue;
4759                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4760                   else if(new == WhiteWazir || new == BlackWazir) {
4761                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4762                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4763                       else boards[moveNum][k][j] = old; // preserve type of Gold
4764                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4765                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4766               }
4767           } else {
4768             /* Move from ICS was illegal!?  Punt. */
4769             if (appData.debugMode) {
4770               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4771               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4772             }
4773             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4774             strcat(parseList[moveNum - 1], " ");
4775             strcat(parseList[moveNum - 1], elapsed_time);
4776             moveList[moveNum - 1][0] = NULLCHAR;
4777             fromX = fromY = toX = toY = -1;
4778           }
4779         }
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4782     setbuf(debugFP, NULL);
4783   }
4784
4785 #if ZIPPY
4786         /* Send move to chess program (BEFORE animating it). */
4787         if (appData.zippyPlay && !newGame && newMove &&
4788            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4789
4790             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4791                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4792                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4793                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4794                             move_str);
4795                     DisplayError(str, 0);
4796                 } else {
4797                     if (first.sendTime) {
4798                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4799                     }
4800                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4801                     if (firstMove && !bookHit) {
4802                         firstMove = FALSE;
4803                         if (first.useColors) {
4804                           SendToProgram(gameMode == IcsPlayingWhite ?
4805                                         "white\ngo\n" :
4806                                         "black\ngo\n", &first);
4807                         } else {
4808                           SendToProgram("go\n", &first);
4809                         }
4810                         first.maybeThinking = TRUE;
4811                     }
4812                 }
4813             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4814               if (moveList[moveNum - 1][0] == NULLCHAR) {
4815                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4816                 DisplayError(str, 0);
4817               } else {
4818                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4819                 SendMoveToProgram(moveNum - 1, &first);
4820               }
4821             }
4822         }
4823 #endif
4824     }
4825
4826     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4827         /* If move comes from a remote source, animate it.  If it
4828            isn't remote, it will have already been animated. */
4829         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4830             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4831         }
4832         if (!pausing && appData.highlightLastMove) {
4833             SetHighlights(fromX, fromY, toX, toY);
4834         }
4835     }
4836
4837     /* Start the clocks */
4838     whiteFlag = blackFlag = FALSE;
4839     appData.clockMode = !(basetime == 0 && increment == 0);
4840     if (ticking == 0) {
4841       ics_clock_paused = TRUE;
4842       StopClocks();
4843     } else if (ticking == 1) {
4844       ics_clock_paused = FALSE;
4845     }
4846     if (gameMode == IcsIdle ||
4847         relation == RELATION_OBSERVING_STATIC ||
4848         relation == RELATION_EXAMINING ||
4849         ics_clock_paused)
4850       DisplayBothClocks();
4851     else
4852       StartClocks();
4853
4854     /* Display opponents and material strengths */
4855     if (gameInfo.variant != VariantBughouse &&
4856         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4857         if (tinyLayout || smallLayout) {
4858             if(gameInfo.variant == VariantNormal)
4859               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4860                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4861                     basetime, increment);
4862             else
4863               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4864                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4865                     basetime, increment, (int) gameInfo.variant);
4866         } else {
4867             if(gameInfo.variant == VariantNormal)
4868               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4869                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4870                     basetime, increment);
4871             else
4872               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4873                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4874                     basetime, increment, VariantName(gameInfo.variant));
4875         }
4876         DisplayTitle(str);
4877   if (appData.debugMode) {
4878     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4879   }
4880     }
4881
4882
4883     /* Display the board */
4884     if (!pausing && !appData.noGUI) {
4885
4886       if (appData.premove)
4887           if (!gotPremove ||
4888              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4889              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4890               ClearPremoveHighlights();
4891
4892       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4893         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4894       DrawPosition(j, boards[currentMove]);
4895
4896       DisplayMove(moveNum - 1);
4897       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4898             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4899               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4900         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4901       }
4902     }
4903
4904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4905 #if ZIPPY
4906     if(bookHit) { // [HGM] book: simulate book reply
4907         static char bookMove[MSG_SIZ]; // a bit generous?
4908
4909         programStats.nodes = programStats.depth = programStats.time =
4910         programStats.score = programStats.got_only_move = 0;
4911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4912
4913         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4914         strcat(bookMove, bookHit);
4915         HandleMachineMove(bookMove, &first);
4916     }
4917 #endif
4918 }
4919
4920 void
4921 GetMoveListEvent ()
4922 {
4923     char buf[MSG_SIZ];
4924     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4925         ics_getting_history = H_REQUESTED;
4926         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4927         SendToICS(buf);
4928     }
4929 }
4930
4931 void
4932 SendToBoth (char *msg)
4933 {   // to make it easy to keep two engines in step in dual analysis
4934     SendToProgram(msg, &first);
4935     if(second.analyzing) SendToProgram(msg, &second);
4936 }
4937
4938 void
4939 AnalysisPeriodicEvent (int force)
4940 {
4941     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4942          && !force) || !appData.periodicUpdates)
4943       return;
4944
4945     /* Send . command to Crafty to collect stats */
4946     SendToBoth(".\n");
4947
4948     /* Don't send another until we get a response (this makes
4949        us stop sending to old Crafty's which don't understand
4950        the "." command (sending illegal cmds resets node count & time,
4951        which looks bad)) */
4952     programStats.ok_to_send = 0;
4953 }
4954
4955 void
4956 ics_update_width (int new_width)
4957 {
4958         ics_printf("set width %d\n", new_width);
4959 }
4960
4961 void
4962 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4963 {
4964     char buf[MSG_SIZ];
4965
4966     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4967         // null move in variant where engine does not understand it (for analysis purposes)
4968         SendBoard(cps, moveNum + 1); // send position after move in stead.
4969         return;
4970     }
4971     if (cps->useUsermove) {
4972       SendToProgram("usermove ", cps);
4973     }
4974     if (cps->useSAN) {
4975       char *space;
4976       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4977         int len = space - parseList[moveNum];
4978         memcpy(buf, parseList[moveNum], len);
4979         buf[len++] = '\n';
4980         buf[len] = NULLCHAR;
4981       } else {
4982         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4983       }
4984       SendToProgram(buf, cps);
4985     } else {
4986       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4987         AlphaRank(moveList[moveNum], 4);
4988         SendToProgram(moveList[moveNum], cps);
4989         AlphaRank(moveList[moveNum], 4); // and back
4990       } else
4991       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4992        * the engine. It would be nice to have a better way to identify castle
4993        * moves here. */
4994       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4995                                                                          && cps->useOOCastle) {
4996         int fromX = moveList[moveNum][0] - AAA;
4997         int fromY = moveList[moveNum][1] - ONE;
4998         int toX = moveList[moveNum][2] - AAA;
4999         int toY = moveList[moveNum][3] - ONE;
5000         if((boards[moveNum][fromY][fromX] == WhiteKing
5001             && boards[moveNum][toY][toX] == WhiteRook)
5002            || (boards[moveNum][fromY][fromX] == BlackKing
5003                && boards[moveNum][toY][toX] == BlackRook)) {
5004           if(toX > fromX) SendToProgram("O-O\n", cps);
5005           else SendToProgram("O-O-O\n", cps);
5006         }
5007         else SendToProgram(moveList[moveNum], cps);
5008       } else
5009       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5010         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5011           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5012           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5013                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5014         } else
5015           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5016                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5017         SendToProgram(buf, cps);
5018       }
5019       else SendToProgram(moveList[moveNum], cps);
5020       /* End of additions by Tord */
5021     }
5022
5023     /* [HGM] setting up the opening has brought engine in force mode! */
5024     /*       Send 'go' if we are in a mode where machine should play. */
5025     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5026         (gameMode == TwoMachinesPlay   ||
5027 #if ZIPPY
5028          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5029 #endif
5030          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5031         SendToProgram("go\n", cps);
5032   if (appData.debugMode) {
5033     fprintf(debugFP, "(extra)\n");
5034   }
5035     }
5036     setboardSpoiledMachineBlack = 0;
5037 }
5038
5039 void
5040 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5041 {
5042     char user_move[MSG_SIZ];
5043     char suffix[4];
5044
5045     if(gameInfo.variant == VariantSChess && promoChar) {
5046         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5047         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5048     } else suffix[0] = NULLCHAR;
5049
5050     switch (moveType) {
5051       default:
5052         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5053                 (int)moveType, fromX, fromY, toX, toY);
5054         DisplayError(user_move + strlen("say "), 0);
5055         break;
5056       case WhiteKingSideCastle:
5057       case BlackKingSideCastle:
5058       case WhiteQueenSideCastleWild:
5059       case BlackQueenSideCastleWild:
5060       /* PUSH Fabien */
5061       case WhiteHSideCastleFR:
5062       case BlackHSideCastleFR:
5063       /* POP Fabien */
5064         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5065         break;
5066       case WhiteQueenSideCastle:
5067       case BlackQueenSideCastle:
5068       case WhiteKingSideCastleWild:
5069       case BlackKingSideCastleWild:
5070       /* PUSH Fabien */
5071       case WhiteASideCastleFR:
5072       case BlackASideCastleFR:
5073       /* POP Fabien */
5074         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5075         break;
5076       case WhiteNonPromotion:
5077       case BlackNonPromotion:
5078         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5079         break;
5080       case WhitePromotion:
5081       case BlackPromotion:
5082         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5083           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5084                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5085                 PieceToChar(WhiteFerz));
5086         else if(gameInfo.variant == VariantGreat)
5087           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5089                 PieceToChar(WhiteMan));
5090         else
5091           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5092                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5093                 promoChar);
5094         break;
5095       case WhiteDrop:
5096       case BlackDrop:
5097       drop:
5098         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5099                  ToUpper(PieceToChar((ChessSquare) fromX)),
5100                  AAA + toX, ONE + toY);
5101         break;
5102       case IllegalMove:  /* could be a variant we don't quite understand */
5103         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5104       case NormalMove:
5105       case WhiteCapturesEnPassant:
5106       case BlackCapturesEnPassant:
5107         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5108                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5109         break;
5110     }
5111     SendToICS(user_move);
5112     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5113         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5114 }
5115
5116 void
5117 UploadGameEvent ()
5118 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5119     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5120     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5121     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5122       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5123       return;
5124     }
5125     if(gameMode != IcsExamining) { // is this ever not the case?
5126         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5127
5128         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5129           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5130         } else { // on FICS we must first go to general examine mode
5131           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5132         }
5133         if(gameInfo.variant != VariantNormal) {
5134             // try figure out wild number, as xboard names are not always valid on ICS
5135             for(i=1; i<=36; i++) {
5136               snprintf(buf, MSG_SIZ, "wild/%d", i);
5137                 if(StringToVariant(buf) == gameInfo.variant) break;
5138             }
5139             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5140             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5141             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5142         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5143         SendToICS(ics_prefix);
5144         SendToICS(buf);
5145         if(startedFromSetupPosition || backwardMostMove != 0) {
5146           fen = PositionToFEN(backwardMostMove, NULL);
5147           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5148             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5149             SendToICS(buf);
5150           } else { // FICS: everything has to set by separate bsetup commands
5151             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5152             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5153             SendToICS(buf);
5154             if(!WhiteOnMove(backwardMostMove)) {
5155                 SendToICS("bsetup tomove black\n");
5156             }
5157             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5158             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5159             SendToICS(buf);
5160             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5161             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5162             SendToICS(buf);
5163             i = boards[backwardMostMove][EP_STATUS];
5164             if(i >= 0) { // set e.p.
5165               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5166                 SendToICS(buf);
5167             }
5168             bsetup++;
5169           }
5170         }
5171       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5172             SendToICS("bsetup done\n"); // switch to normal examining.
5173     }
5174     for(i = backwardMostMove; i<last; i++) {
5175         char buf[20];
5176         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5177         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5178             int len = strlen(moveList[i]);
5179             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5180             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5181         }
5182         SendToICS(buf);
5183     }
5184     SendToICS(ics_prefix);
5185     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5186 }
5187
5188 void
5189 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5190 {
5191     if (rf == DROP_RANK) {
5192       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5193       sprintf(move, "%c@%c%c\n",
5194                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5195     } else {
5196         if (promoChar == 'x' || promoChar == NULLCHAR) {
5197           sprintf(move, "%c%c%c%c\n",
5198                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5199         } else {
5200             sprintf(move, "%c%c%c%c%c\n",
5201                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5202         }
5203     }
5204 }
5205
5206 void
5207 ProcessICSInitScript (FILE *f)
5208 {
5209     char buf[MSG_SIZ];
5210
5211     while (fgets(buf, MSG_SIZ, f)) {
5212         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5213     }
5214
5215     fclose(f);
5216 }
5217
5218
5219 static int lastX, lastY, selectFlag, dragging;
5220
5221 void
5222 Sweep (int step)
5223 {
5224     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5225     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5226     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5227     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5228     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5229     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5230     do {
5231         promoSweep -= step;
5232         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5233         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5234         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5235         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5236         if(!step) step = -1;
5237     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5238             appData.testLegality && (promoSweep == king ||
5239             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5240     if(toX >= 0) {
5241         int victim = boards[currentMove][toY][toX];
5242         boards[currentMove][toY][toX] = promoSweep;
5243         DrawPosition(FALSE, boards[currentMove]);
5244         boards[currentMove][toY][toX] = victim;
5245     } else
5246     ChangeDragPiece(promoSweep);
5247 }
5248
5249 int
5250 PromoScroll (int x, int y)
5251 {
5252   int step = 0;
5253
5254   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5255   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5256   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5257   if(!step) return FALSE;
5258   lastX = x; lastY = y;
5259   if((promoSweep < BlackPawn) == flipView) step = -step;
5260   if(step > 0) selectFlag = 1;
5261   if(!selectFlag) Sweep(step);
5262   return FALSE;
5263 }
5264
5265 void
5266 NextPiece (int step)
5267 {
5268     ChessSquare piece = boards[currentMove][toY][toX];
5269     do {
5270         pieceSweep -= step;
5271         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5272         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5273         if(!step) step = -1;
5274     } while(PieceToChar(pieceSweep) == '.');
5275     boards[currentMove][toY][toX] = pieceSweep;
5276     DrawPosition(FALSE, boards[currentMove]);
5277     boards[currentMove][toY][toX] = piece;
5278 }
5279 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5280 void
5281 AlphaRank (char *move, int n)
5282 {
5283 //    char *p = move, c; int x, y;
5284
5285     if (appData.debugMode) {
5286         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5287     }
5288
5289     if(move[1]=='*' &&
5290        move[2]>='0' && move[2]<='9' &&
5291        move[3]>='a' && move[3]<='x'    ) {
5292         move[1] = '@';
5293         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5294         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5295     } else
5296     if(move[0]>='0' && move[0]<='9' &&
5297        move[1]>='a' && move[1]<='x' &&
5298        move[2]>='0' && move[2]<='9' &&
5299        move[3]>='a' && move[3]<='x'    ) {
5300         /* input move, Shogi -> normal */
5301         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5302         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5303         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5304         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5305     } else
5306     if(move[1]=='@' &&
5307        move[3]>='0' && move[3]<='9' &&
5308        move[2]>='a' && move[2]<='x'    ) {
5309         move[1] = '*';
5310         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5311         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5312     } else
5313     if(
5314        move[0]>='a' && move[0]<='x' &&
5315        move[3]>='0' && move[3]<='9' &&
5316        move[2]>='a' && move[2]<='x'    ) {
5317          /* output move, normal -> Shogi */
5318         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5319         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5320         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5321         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5322         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5323     }
5324     if (appData.debugMode) {
5325         fprintf(debugFP, "   out = '%s'\n", move);
5326     }
5327 }
5328
5329 char yy_textstr[8000];
5330
5331 /* Parser for moves from gnuchess, ICS, or user typein box */
5332 Boolean
5333 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5334 {
5335     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5336
5337     switch (*moveType) {
5338       case WhitePromotion:
5339       case BlackPromotion:
5340       case WhiteNonPromotion:
5341       case BlackNonPromotion:
5342       case NormalMove:
5343       case WhiteCapturesEnPassant:
5344       case BlackCapturesEnPassant:
5345       case WhiteKingSideCastle:
5346       case WhiteQueenSideCastle:
5347       case BlackKingSideCastle:
5348       case BlackQueenSideCastle:
5349       case WhiteKingSideCastleWild:
5350       case WhiteQueenSideCastleWild:
5351       case BlackKingSideCastleWild:
5352       case BlackQueenSideCastleWild:
5353       /* Code added by Tord: */
5354       case WhiteHSideCastleFR:
5355       case WhiteASideCastleFR:
5356       case BlackHSideCastleFR:
5357       case BlackASideCastleFR:
5358       /* End of code added by Tord */
5359       case IllegalMove:         /* bug or odd chess variant */
5360         *fromX = currentMoveString[0] - AAA;
5361         *fromY = currentMoveString[1] - ONE;
5362         *toX = currentMoveString[2] - AAA;
5363         *toY = currentMoveString[3] - ONE;
5364         *promoChar = currentMoveString[4];
5365         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5366             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5367     if (appData.debugMode) {
5368         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5369     }
5370             *fromX = *fromY = *toX = *toY = 0;
5371             return FALSE;
5372         }
5373         if (appData.testLegality) {
5374           return (*moveType != IllegalMove);
5375         } else {
5376           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5377                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5378         }
5379
5380       case WhiteDrop:
5381       case BlackDrop:
5382         *fromX = *moveType == WhiteDrop ?
5383           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5384           (int) CharToPiece(ToLower(currentMoveString[0]));
5385         *fromY = DROP_RANK;
5386         *toX = currentMoveString[2] - AAA;
5387         *toY = currentMoveString[3] - ONE;
5388         *promoChar = NULLCHAR;
5389         return TRUE;
5390
5391       case AmbiguousMove:
5392       case ImpossibleMove:
5393       case EndOfFile:
5394       case ElapsedTime:
5395       case Comment:
5396       case PGNTag:
5397       case NAG:
5398       case WhiteWins:
5399       case BlackWins:
5400       case GameIsDrawn:
5401       default:
5402     if (appData.debugMode) {
5403         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5404     }
5405         /* bug? */
5406         *fromX = *fromY = *toX = *toY = 0;
5407         *promoChar = NULLCHAR;
5408         return FALSE;
5409     }
5410 }
5411
5412 Boolean pushed = FALSE;
5413 char *lastParseAttempt;
5414
5415 void
5416 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5417 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5418   int fromX, fromY, toX, toY; char promoChar;
5419   ChessMove moveType;
5420   Boolean valid;
5421   int nr = 0;
5422
5423   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5424   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5425     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5426     pushed = TRUE;
5427   }
5428   endPV = forwardMostMove;
5429   do {
5430     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5431     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5432     lastParseAttempt = pv;
5433     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5434     if(!valid && nr == 0 &&
5435        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5436         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5437         // Hande case where played move is different from leading PV move
5438         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5439         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5440         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5441         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5442           endPV += 2; // if position different, keep this
5443           moveList[endPV-1][0] = fromX + AAA;
5444           moveList[endPV-1][1] = fromY + ONE;
5445           moveList[endPV-1][2] = toX + AAA;
5446           moveList[endPV-1][3] = toY + ONE;
5447           parseList[endPV-1][0] = NULLCHAR;
5448           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5449         }
5450       }
5451     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5452     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5453     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5454     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5455         valid++; // allow comments in PV
5456         continue;
5457     }
5458     nr++;
5459     if(endPV+1 > framePtr) break; // no space, truncate
5460     if(!valid) break;
5461     endPV++;
5462     CopyBoard(boards[endPV], boards[endPV-1]);
5463     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5464     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5465     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5466     CoordsToAlgebraic(boards[endPV - 1],
5467                              PosFlags(endPV - 1),
5468                              fromY, fromX, toY, toX, promoChar,
5469                              parseList[endPV - 1]);
5470   } while(valid);
5471   if(atEnd == 2) return; // used hidden, for PV conversion
5472   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5473   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5474   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5475                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5476   DrawPosition(TRUE, boards[currentMove]);
5477 }
5478
5479 int
5480 MultiPV (ChessProgramState *cps)
5481 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5482         int i;
5483         for(i=0; i<cps->nrOptions; i++)
5484             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5485                 return i;
5486         return -1;
5487 }
5488
5489 Boolean
5490 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5491 {
5492         int startPV, multi, lineStart, origIndex = index;
5493         char *p, buf2[MSG_SIZ];
5494         ChessProgramState *cps = (pane ? &second : &first);
5495
5496         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5497         lastX = x; lastY = y;
5498         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5499         lineStart = startPV = index;
5500         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5501         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5502         index = startPV;
5503         do{ while(buf[index] && buf[index] != '\n') index++;
5504         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5505         buf[index] = 0;
5506         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5507                 int n = cps->option[multi].value;
5508                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5509                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5510                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5511                 cps->option[multi].value = n;
5512                 *start = *end = 0;
5513                 return FALSE;
5514         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5515                 ExcludeClick(origIndex - lineStart);
5516                 return FALSE;
5517         }
5518         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5519         *start = startPV; *end = index-1;
5520         return TRUE;
5521 }
5522
5523 char *
5524 PvToSAN (char *pv)
5525 {
5526         static char buf[10*MSG_SIZ];
5527         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5528         *buf = NULLCHAR;
5529         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5530         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5531         for(i = forwardMostMove; i<endPV; i++){
5532             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5533             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5534             k += strlen(buf+k);
5535         }
5536         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5537         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5538         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5539         endPV = savedEnd;
5540         return buf;
5541 }
5542
5543 Boolean
5544 LoadPV (int x, int y)
5545 { // called on right mouse click to load PV
5546   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5547   lastX = x; lastY = y;
5548   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5549   return TRUE;
5550 }
5551
5552 void
5553 UnLoadPV ()
5554 {
5555   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5556   if(endPV < 0) return;
5557   if(appData.autoCopyPV) CopyFENToClipboard();
5558   endPV = -1;
5559   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5560         Boolean saveAnimate = appData.animate;
5561         if(pushed) {
5562             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5563                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5564             } else storedGames--; // abandon shelved tail of original game
5565         }
5566         pushed = FALSE;
5567         forwardMostMove = currentMove;
5568         currentMove = oldFMM;
5569         appData.animate = FALSE;
5570         ToNrEvent(forwardMostMove);
5571         appData.animate = saveAnimate;
5572   }
5573   currentMove = forwardMostMove;
5574   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5575   ClearPremoveHighlights();
5576   DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 MovePV (int x, int y, int h)
5581 { // step through PV based on mouse coordinates (called on mouse move)
5582   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5583
5584   // we must somehow check if right button is still down (might be released off board!)
5585   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5586   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5587   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5588   if(!step) return;
5589   lastX = x; lastY = y;
5590
5591   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5592   if(endPV < 0) return;
5593   if(y < margin) step = 1; else
5594   if(y > h - margin) step = -1;
5595   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5596   currentMove += step;
5597   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5598   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5599                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5600   DrawPosition(FALSE, boards[currentMove]);
5601 }
5602
5603
5604 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5605 // All positions will have equal probability, but the current method will not provide a unique
5606 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5607 #define DARK 1
5608 #define LITE 2
5609 #define ANY 3
5610
5611 int squaresLeft[4];
5612 int piecesLeft[(int)BlackPawn];
5613 int seed, nrOfShuffles;
5614
5615 void
5616 GetPositionNumber ()
5617 {       // sets global variable seed
5618         int i;
5619
5620         seed = appData.defaultFrcPosition;
5621         if(seed < 0) { // randomize based on time for negative FRC position numbers
5622                 for(i=0; i<50; i++) seed += random();
5623                 seed = random() ^ random() >> 8 ^ random() << 8;
5624                 if(seed<0) seed = -seed;
5625         }
5626 }
5627
5628 int
5629 put (Board board, int pieceType, int rank, int n, int shade)
5630 // put the piece on the (n-1)-th empty squares of the given shade
5631 {
5632         int i;
5633
5634         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5635                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5636                         board[rank][i] = (ChessSquare) pieceType;
5637                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5638                         squaresLeft[ANY]--;
5639                         piecesLeft[pieceType]--;
5640                         return i;
5641                 }
5642         }
5643         return -1;
5644 }
5645
5646
5647 void
5648 AddOnePiece (Board board, int pieceType, int rank, int shade)
5649 // calculate where the next piece goes, (any empty square), and put it there
5650 {
5651         int i;
5652
5653         i = seed % squaresLeft[shade];
5654         nrOfShuffles *= squaresLeft[shade];
5655         seed /= squaresLeft[shade];
5656         put(board, pieceType, rank, i, shade);
5657 }
5658
5659 void
5660 AddTwoPieces (Board board, int pieceType, int rank)
5661 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5662 {
5663         int i, n=squaresLeft[ANY], j=n-1, k;
5664
5665         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5666         i = seed % k;  // pick one
5667         nrOfShuffles *= k;
5668         seed /= k;
5669         while(i >= j) i -= j--;
5670         j = n - 1 - j; i += j;
5671         put(board, pieceType, rank, j, ANY);
5672         put(board, pieceType, rank, i, ANY);
5673 }
5674
5675 void
5676 SetUpShuffle (Board board, int number)
5677 {
5678         int i, p, first=1;
5679
5680         GetPositionNumber(); nrOfShuffles = 1;
5681
5682         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5683         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5684         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5685
5686         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5687
5688         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5689             p = (int) board[0][i];
5690             if(p < (int) BlackPawn) piecesLeft[p] ++;
5691             board[0][i] = EmptySquare;
5692         }
5693
5694         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5695             // shuffles restricted to allow normal castling put KRR first
5696             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5697                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5698             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5699                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5700             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5701                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5702             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5703                 put(board, WhiteRook, 0, 0, ANY);
5704             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5705         }
5706
5707         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5708             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5709             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5710                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5711                 while(piecesLeft[p] >= 2) {
5712                     AddOnePiece(board, p, 0, LITE);
5713                     AddOnePiece(board, p, 0, DARK);
5714                 }
5715                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5716             }
5717
5718         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5719             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5720             // but we leave King and Rooks for last, to possibly obey FRC restriction
5721             if(p == (int)WhiteRook) continue;
5722             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5723             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5724         }
5725
5726         // now everything is placed, except perhaps King (Unicorn) and Rooks
5727
5728         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5729             // Last King gets castling rights
5730             while(piecesLeft[(int)WhiteUnicorn]) {
5731                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5732                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5733             }
5734
5735             while(piecesLeft[(int)WhiteKing]) {
5736                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5737                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5738             }
5739
5740
5741         } else {
5742             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5743             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5744         }
5745
5746         // Only Rooks can be left; simply place them all
5747         while(piecesLeft[(int)WhiteRook]) {
5748                 i = put(board, WhiteRook, 0, 0, ANY);
5749                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5750                         if(first) {
5751                                 first=0;
5752                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5753                         }
5754                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5755                 }
5756         }
5757         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5758             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5759         }
5760
5761         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5762 }
5763
5764 int
5765 SetCharTable (char *table, const char * map)
5766 /* [HGM] moved here from winboard.c because of its general usefulness */
5767 /*       Basically a safe strcpy that uses the last character as King */
5768 {
5769     int result = FALSE; int NrPieces;
5770
5771     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5772                     && NrPieces >= 12 && !(NrPieces&1)) {
5773         int i; /* [HGM] Accept even length from 12 to 34 */
5774
5775         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5776         for( i=0; i<NrPieces/2-1; i++ ) {
5777             table[i] = map[i];
5778             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5779         }
5780         table[(int) WhiteKing]  = map[NrPieces/2-1];
5781         table[(int) BlackKing]  = map[NrPieces-1];
5782
5783         result = TRUE;
5784     }
5785
5786     return result;
5787 }
5788
5789 void
5790 Prelude (Board board)
5791 {       // [HGM] superchess: random selection of exo-pieces
5792         int i, j, k; ChessSquare p;
5793         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5794
5795         GetPositionNumber(); // use FRC position number
5796
5797         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5798             SetCharTable(pieceToChar, appData.pieceToCharTable);
5799             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5800                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5801         }
5802
5803         j = seed%4;                 seed /= 4;
5804         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5805         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5806         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5807         j = seed%3 + (seed%3 >= j); seed /= 3;
5808         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5809         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5810         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5811         j = seed%3;                 seed /= 3;
5812         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5813         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5814         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5815         j = seed%2 + (seed%2 >= j); seed /= 2;
5816         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5817         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5818         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5819         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5820         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5821         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5822         put(board, exoPieces[0],    0, 0, ANY);
5823         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5824 }
5825
5826 void
5827 InitPosition (int redraw)
5828 {
5829     ChessSquare (* pieces)[BOARD_FILES];
5830     int i, j, pawnRow, overrule,
5831     oldx = gameInfo.boardWidth,
5832     oldy = gameInfo.boardHeight,
5833     oldh = gameInfo.holdingsWidth;
5834     static int oldv;
5835
5836     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5837
5838     /* [AS] Initialize pv info list [HGM] and game status */
5839     {
5840         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5841             pvInfoList[i].depth = 0;
5842             boards[i][EP_STATUS] = EP_NONE;
5843             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5844         }
5845
5846         initialRulePlies = 0; /* 50-move counter start */
5847
5848         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5849         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5850     }
5851
5852
5853     /* [HGM] logic here is completely changed. In stead of full positions */
5854     /* the initialized data only consist of the two backranks. The switch */
5855     /* selects which one we will use, which is than copied to the Board   */
5856     /* initialPosition, which for the rest is initialized by Pawns and    */
5857     /* empty squares. This initial position is then copied to boards[0],  */
5858     /* possibly after shuffling, so that it remains available.            */
5859
5860     gameInfo.holdingsWidth = 0; /* default board sizes */
5861     gameInfo.boardWidth    = 8;
5862     gameInfo.boardHeight   = 8;
5863     gameInfo.holdingsSize  = 0;
5864     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5865     for(i=0; i<BOARD_FILES-2; i++)
5866       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5867     initialPosition[EP_STATUS] = EP_NONE;
5868     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5869     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5870          SetCharTable(pieceNickName, appData.pieceNickNames);
5871     else SetCharTable(pieceNickName, "............");
5872     pieces = FIDEArray;
5873
5874     switch (gameInfo.variant) {
5875     case VariantFischeRandom:
5876       shuffleOpenings = TRUE;
5877     default:
5878       break;
5879     case VariantShatranj:
5880       pieces = ShatranjArray;
5881       nrCastlingRights = 0;
5882       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5883       break;
5884     case VariantMakruk:
5885       pieces = makrukArray;
5886       nrCastlingRights = 0;
5887       startedFromSetupPosition = TRUE;
5888       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5889       break;
5890     case VariantTwoKings:
5891       pieces = twoKingsArray;
5892       break;
5893     case VariantGrand:
5894       pieces = GrandArray;
5895       nrCastlingRights = 0;
5896       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5897       gameInfo.boardWidth = 10;
5898       gameInfo.boardHeight = 10;
5899       gameInfo.holdingsSize = 7;
5900       break;
5901     case VariantCapaRandom:
5902       shuffleOpenings = TRUE;
5903     case VariantCapablanca:
5904       pieces = CapablancaArray;
5905       gameInfo.boardWidth = 10;
5906       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5907       break;
5908     case VariantGothic:
5909       pieces = GothicArray;
5910       gameInfo.boardWidth = 10;
5911       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5912       break;
5913     case VariantSChess:
5914       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5915       gameInfo.holdingsSize = 7;
5916       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5917       break;
5918     case VariantJanus:
5919       pieces = JanusArray;
5920       gameInfo.boardWidth = 10;
5921       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5922       nrCastlingRights = 6;
5923         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5924         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5925         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5926         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5927         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5928         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5929       break;
5930     case VariantFalcon:
5931       pieces = FalconArray;
5932       gameInfo.boardWidth = 10;
5933       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5934       break;
5935     case VariantXiangqi:
5936       pieces = XiangqiArray;
5937       gameInfo.boardWidth  = 9;
5938       gameInfo.boardHeight = 10;
5939       nrCastlingRights = 0;
5940       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5941       break;
5942     case VariantShogi:
5943       pieces = ShogiArray;
5944       gameInfo.boardWidth  = 9;
5945       gameInfo.boardHeight = 9;
5946       gameInfo.holdingsSize = 7;
5947       nrCastlingRights = 0;
5948       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5949       break;
5950     case VariantCourier:
5951       pieces = CourierArray;
5952       gameInfo.boardWidth  = 12;
5953       nrCastlingRights = 0;
5954       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5955       break;
5956     case VariantKnightmate:
5957       pieces = KnightmateArray;
5958       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5959       break;
5960     case VariantSpartan:
5961       pieces = SpartanArray;
5962       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5963       break;
5964     case VariantFairy:
5965       pieces = fairyArray;
5966       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5967       break;
5968     case VariantGreat:
5969       pieces = GreatArray;
5970       gameInfo.boardWidth = 10;
5971       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5972       gameInfo.holdingsSize = 8;
5973       break;
5974     case VariantSuper:
5975       pieces = FIDEArray;
5976       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5977       gameInfo.holdingsSize = 8;
5978       startedFromSetupPosition = TRUE;
5979       break;
5980     case VariantCrazyhouse:
5981     case VariantBughouse:
5982       pieces = FIDEArray;
5983       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5984       gameInfo.holdingsSize = 5;
5985       break;
5986     case VariantWildCastle:
5987       pieces = FIDEArray;
5988       /* !!?shuffle with kings guaranteed to be on d or e file */
5989       shuffleOpenings = 1;
5990       break;
5991     case VariantNoCastle:
5992       pieces = FIDEArray;
5993       nrCastlingRights = 0;
5994       /* !!?unconstrained back-rank shuffle */
5995       shuffleOpenings = 1;
5996       break;
5997     }
5998
5999     overrule = 0;
6000     if(appData.NrFiles >= 0) {
6001         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6002         gameInfo.boardWidth = appData.NrFiles;
6003     }
6004     if(appData.NrRanks >= 0) {
6005         gameInfo.boardHeight = appData.NrRanks;
6006     }
6007     if(appData.holdingsSize >= 0) {
6008         i = appData.holdingsSize;
6009         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6010         gameInfo.holdingsSize = i;
6011     }
6012     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6013     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6014         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6015
6016     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6017     if(pawnRow < 1) pawnRow = 1;
6018     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6019
6020     /* User pieceToChar list overrules defaults */
6021     if(appData.pieceToCharTable != NULL)
6022         SetCharTable(pieceToChar, appData.pieceToCharTable);
6023
6024     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6025
6026         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6027             s = (ChessSquare) 0; /* account holding counts in guard band */
6028         for( i=0; i<BOARD_HEIGHT; i++ )
6029             initialPosition[i][j] = s;
6030
6031         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6032         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6033         initialPosition[pawnRow][j] = WhitePawn;
6034         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6035         if(gameInfo.variant == VariantXiangqi) {
6036             if(j&1) {
6037                 initialPosition[pawnRow][j] =
6038                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6039                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6040                    initialPosition[2][j] = WhiteCannon;
6041                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6042                 }
6043             }
6044         }
6045         if(gameInfo.variant == VariantGrand) {
6046             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6047                initialPosition[0][j] = WhiteRook;
6048                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6049             }
6050         }
6051         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6052     }
6053     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6054
6055             j=BOARD_LEFT+1;
6056             initialPosition[1][j] = WhiteBishop;
6057             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6058             j=BOARD_RGHT-2;
6059             initialPosition[1][j] = WhiteRook;
6060             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6061     }
6062
6063     if( nrCastlingRights == -1) {
6064         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6065         /*       This sets default castling rights from none to normal corners   */
6066         /* Variants with other castling rights must set them themselves above    */
6067         nrCastlingRights = 6;
6068
6069         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6070         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6071         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6072         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6073         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6074         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6075      }
6076
6077      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6078      if(gameInfo.variant == VariantGreat) { // promotion commoners
6079         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6080         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6081         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6082         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6083      }
6084      if( gameInfo.variant == VariantSChess ) {
6085       initialPosition[1][0] = BlackMarshall;
6086       initialPosition[2][0] = BlackAngel;
6087       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6088       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6089       initialPosition[1][1] = initialPosition[2][1] =
6090       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6091      }
6092   if (appData.debugMode) {
6093     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6094   }
6095     if(shuffleOpenings) {
6096         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6097         startedFromSetupPosition = TRUE;
6098     }
6099     if(startedFromPositionFile) {
6100       /* [HGM] loadPos: use PositionFile for every new game */
6101       CopyBoard(initialPosition, filePosition);
6102       for(i=0; i<nrCastlingRights; i++)
6103           initialRights[i] = filePosition[CASTLING][i];
6104       startedFromSetupPosition = TRUE;
6105     }
6106
6107     CopyBoard(boards[0], initialPosition);
6108
6109     if(oldx != gameInfo.boardWidth ||
6110        oldy != gameInfo.boardHeight ||
6111        oldv != gameInfo.variant ||
6112        oldh != gameInfo.holdingsWidth
6113                                          )
6114             InitDrawingSizes(-2 ,0);
6115
6116     oldv = gameInfo.variant;
6117     if (redraw)
6118       DrawPosition(TRUE, boards[currentMove]);
6119 }
6120
6121 void
6122 SendBoard (ChessProgramState *cps, int moveNum)
6123 {
6124     char message[MSG_SIZ];
6125
6126     if (cps->useSetboard) {
6127       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6128       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6129       SendToProgram(message, cps);
6130       free(fen);
6131
6132     } else {
6133       ChessSquare *bp;
6134       int i, j, left=0, right=BOARD_WIDTH;
6135       /* Kludge to set black to move, avoiding the troublesome and now
6136        * deprecated "black" command.
6137        */
6138       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6139         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6140
6141       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6142
6143       SendToProgram("edit\n", cps);
6144       SendToProgram("#\n", cps);
6145       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6146         bp = &boards[moveNum][i][left];
6147         for (j = left; j < right; j++, bp++) {
6148           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6149           if ((int) *bp < (int) BlackPawn) {
6150             if(j == BOARD_RGHT+1)
6151                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6152             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6153             if(message[0] == '+' || message[0] == '~') {
6154               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6155                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6156                         AAA + j, ONE + i);
6157             }
6158             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6159                 message[1] = BOARD_RGHT   - 1 - j + '1';
6160                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6161             }
6162             SendToProgram(message, cps);
6163           }
6164         }
6165       }
6166
6167       SendToProgram("c\n", cps);
6168       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6169         bp = &boards[moveNum][i][left];
6170         for (j = left; j < right; j++, bp++) {
6171           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6172           if (((int) *bp != (int) EmptySquare)
6173               && ((int) *bp >= (int) BlackPawn)) {
6174             if(j == BOARD_LEFT-2)
6175                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6176             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6177                     AAA + j, ONE + i);
6178             if(message[0] == '+' || message[0] == '~') {
6179               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6180                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6181                         AAA + j, ONE + i);
6182             }
6183             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6184                 message[1] = BOARD_RGHT   - 1 - j + '1';
6185                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6186             }
6187             SendToProgram(message, cps);
6188           }
6189         }
6190       }
6191
6192       SendToProgram(".\n", cps);
6193     }
6194     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6195 }
6196
6197 char exclusionHeader[MSG_SIZ];
6198 int exCnt, excludePtr;
6199 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6200 static Exclusion excluTab[200];
6201 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6202
6203 static void
6204 WriteMap (int s)
6205 {
6206     int j;
6207     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6208     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6209 }
6210
6211 static void
6212 ClearMap ()
6213 {
6214     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6215     excludePtr = 24; exCnt = 0;
6216     WriteMap(0);
6217 }
6218
6219 static void
6220 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6221 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6222     char buf[2*MOVE_LEN], *p;
6223     Exclusion *e = excluTab;
6224     int i;
6225     for(i=0; i<exCnt; i++)
6226         if(e[i].ff == fromX && e[i].fr == fromY &&
6227            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6228     if(i == exCnt) { // was not in exclude list; add it
6229         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6230         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6231             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6232             return; // abort
6233         }
6234         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6235         excludePtr++; e[i].mark = excludePtr++;
6236         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6237         exCnt++;
6238     }
6239     exclusionHeader[e[i].mark] = state;
6240 }
6241
6242 static int
6243 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6244 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6245     char buf[MSG_SIZ];
6246     int j, k;
6247     ChessMove moveType;
6248     if((signed char)promoChar == -1) { // kludge to indicate best move
6249         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6250             return 1; // if unparsable, abort
6251     }
6252     // update exclusion map (resolving toggle by consulting existing state)
6253     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6254     j = k%8; k >>= 3;
6255     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6256     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6257          excludeMap[k] |=   1<<j;
6258     else excludeMap[k] &= ~(1<<j);
6259     // update header
6260     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6261     // inform engine
6262     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6263     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6264     SendToBoth(buf);
6265     return (state == '+');
6266 }
6267
6268 static void
6269 ExcludeClick (int index)
6270 {
6271     int i, j;
6272     Exclusion *e = excluTab;
6273     if(index < 25) { // none, best or tail clicked
6274         if(index < 13) { // none: include all
6275             WriteMap(0); // clear map
6276             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6277             SendToBoth("include all\n"); // and inform engine
6278         } else if(index > 18) { // tail
6279             if(exclusionHeader[19] == '-') { // tail was excluded
6280                 SendToBoth("include all\n");
6281                 WriteMap(0); // clear map completely
6282                 // now re-exclude selected moves
6283                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6284                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6285             } else { // tail was included or in mixed state
6286                 SendToBoth("exclude all\n");
6287                 WriteMap(0xFF); // fill map completely
6288                 // now re-include selected moves
6289                 j = 0; // count them
6290                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6291                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6292                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6293             }
6294         } else { // best
6295             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6296         }
6297     } else {
6298         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6299             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6300             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6301             break;
6302         }
6303     }
6304 }
6305
6306 ChessSquare
6307 DefaultPromoChoice (int white)
6308 {
6309     ChessSquare result;
6310     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6311         result = WhiteFerz; // no choice
6312     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6313         result= WhiteKing; // in Suicide Q is the last thing we want
6314     else if(gameInfo.variant == VariantSpartan)
6315         result = white ? WhiteQueen : WhiteAngel;
6316     else result = WhiteQueen;
6317     if(!white) result = WHITE_TO_BLACK result;
6318     return result;
6319 }
6320
6321 static int autoQueen; // [HGM] oneclick
6322
6323 int
6324 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6325 {
6326     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6327     /* [HGM] add Shogi promotions */
6328     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6329     ChessSquare piece;
6330     ChessMove moveType;
6331     Boolean premove;
6332
6333     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6334     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6335
6336     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6337       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6338         return FALSE;
6339
6340     piece = boards[currentMove][fromY][fromX];
6341     if(gameInfo.variant == VariantShogi) {
6342         promotionZoneSize = BOARD_HEIGHT/3;
6343         highestPromotingPiece = (int)WhiteFerz;
6344     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6345         promotionZoneSize = 3;
6346     }
6347
6348     // Treat Lance as Pawn when it is not representing Amazon
6349     if(gameInfo.variant != VariantSuper) {
6350         if(piece == WhiteLance) piece = WhitePawn; else
6351         if(piece == BlackLance) piece = BlackPawn;
6352     }
6353
6354     // next weed out all moves that do not touch the promotion zone at all
6355     if((int)piece >= BlackPawn) {
6356         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6357              return FALSE;
6358         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6359     } else {
6360         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6361            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6362     }
6363
6364     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6365
6366     // weed out mandatory Shogi promotions
6367     if(gameInfo.variant == VariantShogi) {
6368         if(piece >= BlackPawn) {
6369             if(toY == 0 && piece == BlackPawn ||
6370                toY == 0 && piece == BlackQueen ||
6371                toY <= 1 && piece == BlackKnight) {
6372                 *promoChoice = '+';
6373                 return FALSE;
6374             }
6375         } else {
6376             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6377                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6378                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6379                 *promoChoice = '+';
6380                 return FALSE;
6381             }
6382         }
6383     }
6384
6385     // weed out obviously illegal Pawn moves
6386     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6387         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6388         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6389         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6390         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6391         // note we are not allowed to test for valid (non-)capture, due to premove
6392     }
6393
6394     // we either have a choice what to promote to, or (in Shogi) whether to promote
6395     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6396         *promoChoice = PieceToChar(BlackFerz);  // no choice
6397         return FALSE;
6398     }
6399     // no sense asking what we must promote to if it is going to explode...
6400     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6401         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6402         return FALSE;
6403     }
6404     // give caller the default choice even if we will not make it
6405     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6406     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6407     if(        sweepSelect && gameInfo.variant != VariantGreat
6408                            && gameInfo.variant != VariantGrand
6409                            && gameInfo.variant != VariantSuper) return FALSE;
6410     if(autoQueen) return FALSE; // predetermined
6411
6412     // suppress promotion popup on illegal moves that are not premoves
6413     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6414               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6415     if(appData.testLegality && !premove) {
6416         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6417                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6418         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6419             return FALSE;
6420     }
6421
6422     return TRUE;
6423 }
6424
6425 int
6426 InPalace (int row, int column)
6427 {   /* [HGM] for Xiangqi */
6428     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6429          column < (BOARD_WIDTH + 4)/2 &&
6430          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6431     return FALSE;
6432 }
6433
6434 int
6435 PieceForSquare (int x, int y)
6436 {
6437   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6438      return -1;
6439   else
6440      return boards[currentMove][y][x];
6441 }
6442
6443 int
6444 OKToStartUserMove (int x, int y)
6445 {
6446     ChessSquare from_piece;
6447     int white_piece;
6448
6449     if (matchMode) return FALSE;
6450     if (gameMode == EditPosition) return TRUE;
6451
6452     if (x >= 0 && y >= 0)
6453       from_piece = boards[currentMove][y][x];
6454     else
6455       from_piece = EmptySquare;
6456
6457     if (from_piece == EmptySquare) return FALSE;
6458
6459     white_piece = (int)from_piece >= (int)WhitePawn &&
6460       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6461
6462     switch (gameMode) {
6463       case AnalyzeFile:
6464       case TwoMachinesPlay:
6465       case EndOfGame:
6466         return FALSE;
6467
6468       case IcsObserving:
6469       case IcsIdle:
6470         return FALSE;
6471
6472       case MachinePlaysWhite:
6473       case IcsPlayingBlack:
6474         if (appData.zippyPlay) return FALSE;
6475         if (white_piece) {
6476             DisplayMoveError(_("You are playing Black"));
6477             return FALSE;
6478         }
6479         break;
6480
6481       case MachinePlaysBlack:
6482       case IcsPlayingWhite:
6483         if (appData.zippyPlay) return FALSE;
6484         if (!white_piece) {
6485             DisplayMoveError(_("You are playing White"));
6486             return FALSE;
6487         }
6488         break;
6489
6490       case PlayFromGameFile:
6491             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6492       case EditGame:
6493         if (!white_piece && WhiteOnMove(currentMove)) {
6494             DisplayMoveError(_("It is White's turn"));
6495             return FALSE;
6496         }
6497         if (white_piece && !WhiteOnMove(currentMove)) {
6498             DisplayMoveError(_("It is Black's turn"));
6499             return FALSE;
6500         }
6501         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6502             /* Editing correspondence game history */
6503             /* Could disallow this or prompt for confirmation */
6504             cmailOldMove = -1;
6505         }
6506         break;
6507
6508       case BeginningOfGame:
6509         if (appData.icsActive) return FALSE;
6510         if (!appData.noChessProgram) {
6511             if (!white_piece) {
6512                 DisplayMoveError(_("You are playing White"));
6513                 return FALSE;
6514             }
6515         }
6516         break;
6517
6518       case Training:
6519         if (!white_piece && WhiteOnMove(currentMove)) {
6520             DisplayMoveError(_("It is White's turn"));
6521             return FALSE;
6522         }
6523         if (white_piece && !WhiteOnMove(currentMove)) {
6524             DisplayMoveError(_("It is Black's turn"));
6525             return FALSE;
6526         }
6527         break;
6528
6529       default:
6530       case IcsExamining:
6531         break;
6532     }
6533     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6534         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6535         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6536         && gameMode != AnalyzeFile && gameMode != Training) {
6537         DisplayMoveError(_("Displayed position is not current"));
6538         return FALSE;
6539     }
6540     return TRUE;
6541 }
6542
6543 Boolean
6544 OnlyMove (int *x, int *y, Boolean captures)
6545 {
6546     DisambiguateClosure cl;
6547     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6548     switch(gameMode) {
6549       case MachinePlaysBlack:
6550       case IcsPlayingWhite:
6551       case BeginningOfGame:
6552         if(!WhiteOnMove(currentMove)) return FALSE;
6553         break;
6554       case MachinePlaysWhite:
6555       case IcsPlayingBlack:
6556         if(WhiteOnMove(currentMove)) return FALSE;
6557         break;
6558       case EditGame:
6559         break;
6560       default:
6561         return FALSE;
6562     }
6563     cl.pieceIn = EmptySquare;
6564     cl.rfIn = *y;
6565     cl.ffIn = *x;
6566     cl.rtIn = -1;
6567     cl.ftIn = -1;
6568     cl.promoCharIn = NULLCHAR;
6569     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6570     if( cl.kind == NormalMove ||
6571         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6572         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6573         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6574       fromX = cl.ff;
6575       fromY = cl.rf;
6576       *x = cl.ft;
6577       *y = cl.rt;
6578       return TRUE;
6579     }
6580     if(cl.kind != ImpossibleMove) return FALSE;
6581     cl.pieceIn = EmptySquare;
6582     cl.rfIn = -1;
6583     cl.ffIn = -1;
6584     cl.rtIn = *y;
6585     cl.ftIn = *x;
6586     cl.promoCharIn = NULLCHAR;
6587     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6588     if( cl.kind == NormalMove ||
6589         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6590         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6591         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6592       fromX = cl.ff;
6593       fromY = cl.rf;
6594       *x = cl.ft;
6595       *y = cl.rt;
6596       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6597       return TRUE;
6598     }
6599     return FALSE;
6600 }
6601
6602 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6603 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6604 int lastLoadGameUseList = FALSE;
6605 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6606 ChessMove lastLoadGameStart = EndOfFile;
6607 int doubleClick;
6608
6609 void
6610 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6611 {
6612     ChessMove moveType;
6613     ChessSquare pup;
6614     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6615
6616     /* Check if the user is playing in turn.  This is complicated because we
6617        let the user "pick up" a piece before it is his turn.  So the piece he
6618        tried to pick up may have been captured by the time he puts it down!
6619        Therefore we use the color the user is supposed to be playing in this
6620        test, not the color of the piece that is currently on the starting
6621        square---except in EditGame mode, where the user is playing both
6622        sides; fortunately there the capture race can't happen.  (It can
6623        now happen in IcsExamining mode, but that's just too bad.  The user
6624        will get a somewhat confusing message in that case.)
6625        */
6626
6627     switch (gameMode) {
6628       case AnalyzeFile:
6629       case TwoMachinesPlay:
6630       case EndOfGame:
6631       case IcsObserving:
6632       case IcsIdle:
6633         /* We switched into a game mode where moves are not accepted,
6634            perhaps while the mouse button was down. */
6635         return;
6636
6637       case MachinePlaysWhite:
6638         /* User is moving for Black */
6639         if (WhiteOnMove(currentMove)) {
6640             DisplayMoveError(_("It is White's turn"));
6641             return;
6642         }
6643         break;
6644
6645       case MachinePlaysBlack:
6646         /* User is moving for White */
6647         if (!WhiteOnMove(currentMove)) {
6648             DisplayMoveError(_("It is Black's turn"));
6649             return;
6650         }
6651         break;
6652
6653       case PlayFromGameFile:
6654             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6655       case EditGame:
6656       case IcsExamining:
6657       case BeginningOfGame:
6658       case AnalyzeMode:
6659       case Training:
6660         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6661         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6662             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6663             /* User is moving for Black */
6664             if (WhiteOnMove(currentMove)) {
6665                 DisplayMoveError(_("It is White's turn"));
6666                 return;
6667             }
6668         } else {
6669             /* User is moving for White */
6670             if (!WhiteOnMove(currentMove)) {
6671                 DisplayMoveError(_("It is Black's turn"));
6672                 return;
6673             }
6674         }
6675         break;
6676
6677       case IcsPlayingBlack:
6678         /* User is moving for Black */
6679         if (WhiteOnMove(currentMove)) {
6680             if (!appData.premove) {
6681                 DisplayMoveError(_("It is White's turn"));
6682             } else if (toX >= 0 && toY >= 0) {
6683                 premoveToX = toX;
6684                 premoveToY = toY;
6685                 premoveFromX = fromX;
6686                 premoveFromY = fromY;
6687                 premovePromoChar = promoChar;
6688                 gotPremove = 1;
6689                 if (appData.debugMode)
6690                     fprintf(debugFP, "Got premove: fromX %d,"
6691                             "fromY %d, toX %d, toY %d\n",
6692                             fromX, fromY, toX, toY);
6693             }
6694             return;
6695         }
6696         break;
6697
6698       case IcsPlayingWhite:
6699         /* User is moving for White */
6700         if (!WhiteOnMove(currentMove)) {
6701             if (!appData.premove) {
6702                 DisplayMoveError(_("It is Black's turn"));
6703             } else if (toX >= 0 && toY >= 0) {
6704                 premoveToX = toX;
6705                 premoveToY = toY;
6706                 premoveFromX = fromX;
6707                 premoveFromY = fromY;
6708                 premovePromoChar = promoChar;
6709                 gotPremove = 1;
6710                 if (appData.debugMode)
6711                     fprintf(debugFP, "Got premove: fromX %d,"
6712                             "fromY %d, toX %d, toY %d\n",
6713                             fromX, fromY, toX, toY);
6714             }
6715             return;
6716         }
6717         break;
6718
6719       default:
6720         break;
6721
6722       case EditPosition:
6723         /* EditPosition, empty square, or different color piece;
6724            click-click move is possible */
6725         if (toX == -2 || toY == -2) {
6726             boards[0][fromY][fromX] = EmptySquare;
6727             DrawPosition(FALSE, boards[currentMove]);
6728             return;
6729         } else if (toX >= 0 && toY >= 0) {
6730             boards[0][toY][toX] = boards[0][fromY][fromX];
6731             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6732                 if(boards[0][fromY][0] != EmptySquare) {
6733                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6734                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6735                 }
6736             } else
6737             if(fromX == BOARD_RGHT+1) {
6738                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6739                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6740                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6741                 }
6742             } else
6743             boards[0][fromY][fromX] = gatingPiece;
6744             DrawPosition(FALSE, boards[currentMove]);
6745             return;
6746         }
6747         return;
6748     }
6749
6750     if(toX < 0 || toY < 0) return;
6751     pup = boards[currentMove][toY][toX];
6752
6753     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6754     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6755          if( pup != EmptySquare ) return;
6756          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6757            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6758                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6759            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6760            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6761            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6762            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6763          fromY = DROP_RANK;
6764     }
6765
6766     /* [HGM] always test for legality, to get promotion info */
6767     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6768                                          fromY, fromX, toY, toX, promoChar);
6769
6770     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6771
6772     /* [HGM] but possibly ignore an IllegalMove result */
6773     if (appData.testLegality) {
6774         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6775             DisplayMoveError(_("Illegal move"));
6776             return;
6777         }
6778     }
6779
6780     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6781         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6782              ClearPremoveHighlights(); // was included
6783         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6784         return;
6785     }
6786
6787     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6788 }
6789
6790 /* Common tail of UserMoveEvent and DropMenuEvent */
6791 int
6792 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6793 {
6794     char *bookHit = 0;
6795
6796     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6797         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6798         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6799         if(WhiteOnMove(currentMove)) {
6800             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6801         } else {
6802             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6803         }
6804     }
6805
6806     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6807        move type in caller when we know the move is a legal promotion */
6808     if(moveType == NormalMove && promoChar)
6809         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6810
6811     /* [HGM] <popupFix> The following if has been moved here from
6812        UserMoveEvent(). Because it seemed to belong here (why not allow
6813        piece drops in training games?), and because it can only be
6814        performed after it is known to what we promote. */
6815     if (gameMode == Training) {
6816       /* compare the move played on the board to the next move in the
6817        * game. If they match, display the move and the opponent's response.
6818        * If they don't match, display an error message.
6819        */
6820       int saveAnimate;
6821       Board testBoard;
6822       CopyBoard(testBoard, boards[currentMove]);
6823       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6824
6825       if (CompareBoards(testBoard, boards[currentMove+1])) {
6826         ForwardInner(currentMove+1);
6827
6828         /* Autoplay the opponent's response.
6829          * if appData.animate was TRUE when Training mode was entered,
6830          * the response will be animated.
6831          */
6832         saveAnimate = appData.animate;
6833         appData.animate = animateTraining;
6834         ForwardInner(currentMove+1);
6835         appData.animate = saveAnimate;
6836
6837         /* check for the end of the game */
6838         if (currentMove >= forwardMostMove) {
6839           gameMode = PlayFromGameFile;
6840           ModeHighlight();
6841           SetTrainingModeOff();
6842           DisplayInformation(_("End of game"));
6843         }
6844       } else {
6845         DisplayError(_("Incorrect move"), 0);
6846       }
6847       return 1;
6848     }
6849
6850   /* Ok, now we know that the move is good, so we can kill
6851      the previous line in Analysis Mode */
6852   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6853                                 && currentMove < forwardMostMove) {
6854     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6855     else forwardMostMove = currentMove;
6856   }
6857
6858   ClearMap();
6859
6860   /* If we need the chess program but it's dead, restart it */
6861   ResurrectChessProgram();
6862
6863   /* A user move restarts a paused game*/
6864   if (pausing)
6865     PauseEvent();
6866
6867   thinkOutput[0] = NULLCHAR;
6868
6869   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6870
6871   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6872     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6873     return 1;
6874   }
6875
6876   if (gameMode == BeginningOfGame) {
6877     if (appData.noChessProgram) {
6878       gameMode = EditGame;
6879       SetGameInfo();
6880     } else {
6881       char buf[MSG_SIZ];
6882       gameMode = MachinePlaysBlack;
6883       StartClocks();
6884       SetGameInfo();
6885       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6886       DisplayTitle(buf);
6887       if (first.sendName) {
6888         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6889         SendToProgram(buf, &first);
6890       }
6891       StartClocks();
6892     }
6893     ModeHighlight();
6894   }
6895
6896   /* Relay move to ICS or chess engine */
6897   if (appData.icsActive) {
6898     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6899         gameMode == IcsExamining) {
6900       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6901         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6902         SendToICS("draw ");
6903         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6904       }
6905       // also send plain move, in case ICS does not understand atomic claims
6906       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6907       ics_user_moved = 1;
6908     }
6909   } else {
6910     if (first.sendTime && (gameMode == BeginningOfGame ||
6911                            gameMode == MachinePlaysWhite ||
6912                            gameMode == MachinePlaysBlack)) {
6913       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6914     }
6915     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6916          // [HGM] book: if program might be playing, let it use book
6917         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6918         first.maybeThinking = TRUE;
6919     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6920         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6921         SendBoard(&first, currentMove+1);
6922         if(second.analyzing) {
6923             if(!second.useSetboard) SendToProgram("undo\n", &second);
6924             SendBoard(&second, currentMove+1);
6925         }
6926     } else {
6927         SendMoveToProgram(forwardMostMove-1, &first);
6928         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6929     }
6930     if (currentMove == cmailOldMove + 1) {
6931       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6932     }
6933   }
6934
6935   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6936
6937   switch (gameMode) {
6938   case EditGame:
6939     if(appData.testLegality)
6940     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6941     case MT_NONE:
6942     case MT_CHECK:
6943       break;
6944     case MT_CHECKMATE:
6945     case MT_STAINMATE:
6946       if (WhiteOnMove(currentMove)) {
6947         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6948       } else {
6949         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6950       }
6951       break;
6952     case MT_STALEMATE:
6953       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6954       break;
6955     }
6956     break;
6957
6958   case MachinePlaysBlack:
6959   case MachinePlaysWhite:
6960     /* disable certain menu options while machine is thinking */
6961     SetMachineThinkingEnables();
6962     break;
6963
6964   default:
6965     break;
6966   }
6967
6968   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6969   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6970
6971   if(bookHit) { // [HGM] book: simulate book reply
6972         static char bookMove[MSG_SIZ]; // a bit generous?
6973
6974         programStats.nodes = programStats.depth = programStats.time =
6975         programStats.score = programStats.got_only_move = 0;
6976         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6977
6978         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6979         strcat(bookMove, bookHit);
6980         HandleMachineMove(bookMove, &first);
6981   }
6982   return 1;
6983 }
6984
6985 void
6986 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6987 {
6988     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6989     Markers *m = (Markers *) closure;
6990     if(rf == fromY && ff == fromX)
6991         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6992                          || kind == WhiteCapturesEnPassant
6993                          || kind == BlackCapturesEnPassant);
6994     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6995 }
6996
6997 void
6998 MarkTargetSquares (int clear)
6999 {
7000   int x, y;
7001   if(clear) // no reason to ever suppress clearing
7002     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7003   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7004      !appData.testLegality || gameMode == EditPosition) return;
7005   if(!clear) {
7006     int capt = 0;
7007     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7008     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7009       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7010       if(capt)
7011       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7012     }
7013   }
7014   DrawPosition(FALSE, NULL);
7015 }
7016
7017 int
7018 Explode (Board board, int fromX, int fromY, int toX, int toY)
7019 {
7020     if(gameInfo.variant == VariantAtomic &&
7021        (board[toY][toX] != EmptySquare ||                     // capture?
7022         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7023                          board[fromY][fromX] == BlackPawn   )
7024       )) {
7025         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7026         return TRUE;
7027     }
7028     return FALSE;
7029 }
7030
7031 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7032
7033 int
7034 CanPromote (ChessSquare piece, int y)
7035 {
7036         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7037         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7038         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7039            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7040            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7041                                                   gameInfo.variant == VariantMakruk) return FALSE;
7042         return (piece == BlackPawn && y == 1 ||
7043                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7044                 piece == BlackLance && y == 1 ||
7045                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7046 }
7047
7048 void
7049 LeftClick (ClickType clickType, int xPix, int yPix)
7050 {
7051     int x, y;
7052     Boolean saveAnimate;
7053     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7054     char promoChoice = NULLCHAR;
7055     ChessSquare piece;
7056     static TimeMark lastClickTime, prevClickTime;
7057
7058     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7059
7060     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7061
7062     if (clickType == Press) ErrorPopDown();
7063
7064     x = EventToSquare(xPix, BOARD_WIDTH);
7065     y = EventToSquare(yPix, BOARD_HEIGHT);
7066     if (!flipView && y >= 0) {
7067         y = BOARD_HEIGHT - 1 - y;
7068     }
7069     if (flipView && x >= 0) {
7070         x = BOARD_WIDTH - 1 - x;
7071     }
7072
7073     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7074         defaultPromoChoice = promoSweep;
7075         promoSweep = EmptySquare;   // terminate sweep
7076         promoDefaultAltered = TRUE;
7077         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7078     }
7079
7080     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7081         if(clickType == Release) return; // ignore upclick of click-click destination
7082         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7083         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7084         if(gameInfo.holdingsWidth &&
7085                 (WhiteOnMove(currentMove)
7086                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7087                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7088             // click in right holdings, for determining promotion piece
7089             ChessSquare p = boards[currentMove][y][x];
7090             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7091             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7092             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7093                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7094                 fromX = fromY = -1;
7095                 return;
7096             }
7097         }
7098         DrawPosition(FALSE, boards[currentMove]);
7099         return;
7100     }
7101
7102     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7103     if(clickType == Press
7104             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7105               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7106               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7107         return;
7108
7109     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7110         // could be static click on premove from-square: abort premove
7111         gotPremove = 0;
7112         ClearPremoveHighlights();
7113     }
7114
7115     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7116         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7117
7118     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7119         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7120                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7121         defaultPromoChoice = DefaultPromoChoice(side);
7122     }
7123
7124     autoQueen = appData.alwaysPromoteToQueen;
7125
7126     if (fromX == -1) {
7127       int originalY = y;
7128       gatingPiece = EmptySquare;
7129       if (clickType != Press) {
7130         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7131             DragPieceEnd(xPix, yPix); dragging = 0;
7132             DrawPosition(FALSE, NULL);
7133         }
7134         return;
7135       }
7136       doubleClick = FALSE;
7137       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7138         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7139       }
7140       fromX = x; fromY = y; toX = toY = -1;
7141       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7142          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7143          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7144             /* First square */
7145             if (OKToStartUserMove(fromX, fromY)) {
7146                 second = 0;
7147                 MarkTargetSquares(0);
7148                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7149                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7150                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7151                     promoSweep = defaultPromoChoice;
7152                     selectFlag = 0; lastX = xPix; lastY = yPix;
7153                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7154                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7155                 }
7156                 if (appData.highlightDragging) {
7157                     SetHighlights(fromX, fromY, -1, -1);
7158                 } else {
7159                     ClearHighlights();
7160                 }
7161             } else fromX = fromY = -1;
7162             return;
7163         }
7164     }
7165
7166     /* fromX != -1 */
7167     if (clickType == Press && gameMode != EditPosition) {
7168         ChessSquare fromP;
7169         ChessSquare toP;
7170         int frc;
7171
7172         // ignore off-board to clicks
7173         if(y < 0 || x < 0) return;
7174
7175         /* Check if clicking again on the same color piece */
7176         fromP = boards[currentMove][fromY][fromX];
7177         toP = boards[currentMove][y][x];
7178         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7179         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7180              WhitePawn <= toP && toP <= WhiteKing &&
7181              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7182              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7183             (BlackPawn <= fromP && fromP <= BlackKing &&
7184              BlackPawn <= toP && toP <= BlackKing &&
7185              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7186              !(fromP == BlackKing && toP == BlackRook && frc))) {
7187             /* Clicked again on same color piece -- changed his mind */
7188             second = (x == fromX && y == fromY);
7189             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7190                 second = FALSE; // first double-click rather than scond click
7191                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7192             }
7193             promoDefaultAltered = FALSE;
7194             MarkTargetSquares(1);
7195            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7196             if (appData.highlightDragging) {
7197                 SetHighlights(x, y, -1, -1);
7198             } else {
7199                 ClearHighlights();
7200             }
7201             if (OKToStartUserMove(x, y)) {
7202                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7203                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7204                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7205                  gatingPiece = boards[currentMove][fromY][fromX];
7206                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7207                 fromX = x;
7208                 fromY = y; dragging = 1;
7209                 MarkTargetSquares(0);
7210                 DragPieceBegin(xPix, yPix, FALSE);
7211                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7212                     promoSweep = defaultPromoChoice;
7213                     selectFlag = 0; lastX = xPix; lastY = yPix;
7214                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7215                 }
7216             }
7217            }
7218            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7219            second = FALSE;
7220         }
7221         // ignore clicks on holdings
7222         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7223     }
7224
7225     if (clickType == Release && x == fromX && y == fromY) {
7226         DragPieceEnd(xPix, yPix); dragging = 0;
7227         if(clearFlag) {
7228             // a deferred attempt to click-click move an empty square on top of a piece
7229             boards[currentMove][y][x] = EmptySquare;
7230             ClearHighlights();
7231             DrawPosition(FALSE, boards[currentMove]);
7232             fromX = fromY = -1; clearFlag = 0;
7233             return;
7234         }
7235         if (appData.animateDragging) {
7236             /* Undo animation damage if any */
7237             DrawPosition(FALSE, NULL);
7238         }
7239         if (second || sweepSelecting) {
7240             /* Second up/down in same square; just abort move */
7241             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7242             second = sweepSelecting = 0;
7243             fromX = fromY = -1;
7244             gatingPiece = EmptySquare;
7245             ClearHighlights();
7246             gotPremove = 0;
7247             ClearPremoveHighlights();
7248         } else {
7249             /* First upclick in same square; start click-click mode */
7250             SetHighlights(x, y, -1, -1);
7251         }
7252         return;
7253     }
7254
7255     clearFlag = 0;
7256
7257     /* we now have a different from- and (possibly off-board) to-square */
7258     /* Completed move */
7259     if(!sweepSelecting) {
7260         toX = x;
7261         toY = y;
7262     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7263
7264     saveAnimate = appData.animate;
7265     if (clickType == Press) {
7266         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7267             // must be Edit Position mode with empty-square selected
7268             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7269             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7270             return;
7271         }
7272         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7273           if(appData.sweepSelect) {
7274             ChessSquare piece = boards[currentMove][fromY][fromX];
7275             promoSweep = defaultPromoChoice;
7276             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7277             selectFlag = 0; lastX = xPix; lastY = yPix;
7278             Sweep(0); // Pawn that is going to promote: preview promotion piece
7279             sweepSelecting = 1;
7280             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7281             MarkTargetSquares(1);
7282           }
7283           return; // promo popup appears on up-click
7284         }
7285         /* Finish clickclick move */
7286         if (appData.animate || appData.highlightLastMove) {
7287             SetHighlights(fromX, fromY, toX, toY);
7288         } else {
7289             ClearHighlights();
7290         }
7291     } else {
7292 #if 0
7293 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7294         /* Finish drag move */
7295         if (appData.highlightLastMove) {
7296             SetHighlights(fromX, fromY, toX, toY);
7297         } else {
7298             ClearHighlights();
7299         }
7300 #endif
7301         DragPieceEnd(xPix, yPix); dragging = 0;
7302         /* Don't animate move and drag both */
7303         appData.animate = FALSE;
7304     }
7305
7306     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7307     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7308         ChessSquare piece = boards[currentMove][fromY][fromX];
7309         if(gameMode == EditPosition && piece != EmptySquare &&
7310            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7311             int n;
7312
7313             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7314                 n = PieceToNumber(piece - (int)BlackPawn);
7315                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7316                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7317                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7318             } else
7319             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7320                 n = PieceToNumber(piece);
7321                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7322                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7323                 boards[currentMove][n][BOARD_WIDTH-2]++;
7324             }
7325             boards[currentMove][fromY][fromX] = EmptySquare;
7326         }
7327         ClearHighlights();
7328         fromX = fromY = -1;
7329         MarkTargetSquares(1);
7330         DrawPosition(TRUE, boards[currentMove]);
7331         return;
7332     }
7333
7334     // off-board moves should not be highlighted
7335     if(x < 0 || y < 0) ClearHighlights();
7336
7337     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7338
7339     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7340         SetHighlights(fromX, fromY, toX, toY);
7341         MarkTargetSquares(1);
7342         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7343             // [HGM] super: promotion to captured piece selected from holdings
7344             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7345             promotionChoice = TRUE;
7346             // kludge follows to temporarily execute move on display, without promoting yet
7347             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7348             boards[currentMove][toY][toX] = p;
7349             DrawPosition(FALSE, boards[currentMove]);
7350             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7351             boards[currentMove][toY][toX] = q;
7352             DisplayMessage("Click in holdings to choose piece", "");
7353             return;
7354         }
7355         PromotionPopUp();
7356     } else {
7357         int oldMove = currentMove;
7358         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7359         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7360         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7361         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7362            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7363             DrawPosition(TRUE, boards[currentMove]);
7364         MarkTargetSquares(1);
7365         fromX = fromY = -1;
7366     }
7367     appData.animate = saveAnimate;
7368     if (appData.animate || appData.animateDragging) {
7369         /* Undo animation damage if needed */
7370         DrawPosition(FALSE, NULL);
7371     }
7372 }
7373
7374 int
7375 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7376 {   // front-end-free part taken out of PieceMenuPopup
7377     int whichMenu; int xSqr, ySqr;
7378
7379     if(seekGraphUp) { // [HGM] seekgraph
7380         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7381         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7382         return -2;
7383     }
7384
7385     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7386          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7387         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7388         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7389         if(action == Press)   {
7390             originalFlip = flipView;
7391             flipView = !flipView; // temporarily flip board to see game from partners perspective
7392             DrawPosition(TRUE, partnerBoard);
7393             DisplayMessage(partnerStatus, "");
7394             partnerUp = TRUE;
7395         } else if(action == Release) {
7396             flipView = originalFlip;
7397             DrawPosition(TRUE, boards[currentMove]);
7398             partnerUp = FALSE;
7399         }
7400         return -2;
7401     }
7402
7403     xSqr = EventToSquare(x, BOARD_WIDTH);
7404     ySqr = EventToSquare(y, BOARD_HEIGHT);
7405     if (action == Release) {
7406         if(pieceSweep != EmptySquare) {
7407             EditPositionMenuEvent(pieceSweep, toX, toY);
7408             pieceSweep = EmptySquare;
7409         } else UnLoadPV(); // [HGM] pv
7410     }
7411     if (action != Press) return -2; // return code to be ignored
7412     switch (gameMode) {
7413       case IcsExamining:
7414         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7415       case EditPosition:
7416         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7417         if (xSqr < 0 || ySqr < 0) return -1;
7418         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7419         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7420         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7421         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7422         NextPiece(0);
7423         return 2; // grab
7424       case IcsObserving:
7425         if(!appData.icsEngineAnalyze) return -1;
7426       case IcsPlayingWhite:
7427       case IcsPlayingBlack:
7428         if(!appData.zippyPlay) goto noZip;
7429       case AnalyzeMode:
7430       case AnalyzeFile:
7431       case MachinePlaysWhite:
7432       case MachinePlaysBlack:
7433       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7434         if (!appData.dropMenu) {
7435           LoadPV(x, y);
7436           return 2; // flag front-end to grab mouse events
7437         }
7438         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7439            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7440       case EditGame:
7441       noZip:
7442         if (xSqr < 0 || ySqr < 0) return -1;
7443         if (!appData.dropMenu || appData.testLegality &&
7444             gameInfo.variant != VariantBughouse &&
7445             gameInfo.variant != VariantCrazyhouse) return -1;
7446         whichMenu = 1; // drop menu
7447         break;
7448       default:
7449         return -1;
7450     }
7451
7452     if (((*fromX = xSqr) < 0) ||
7453         ((*fromY = ySqr) < 0)) {
7454         *fromX = *fromY = -1;
7455         return -1;
7456     }
7457     if (flipView)
7458       *fromX = BOARD_WIDTH - 1 - *fromX;
7459     else
7460       *fromY = BOARD_HEIGHT - 1 - *fromY;
7461
7462     return whichMenu;
7463 }
7464
7465 void
7466 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7467 {
7468 //    char * hint = lastHint;
7469     FrontEndProgramStats stats;
7470
7471     stats.which = cps == &first ? 0 : 1;
7472     stats.depth = cpstats->depth;
7473     stats.nodes = cpstats->nodes;
7474     stats.score = cpstats->score;
7475     stats.time = cpstats->time;
7476     stats.pv = cpstats->movelist;
7477     stats.hint = lastHint;
7478     stats.an_move_index = 0;
7479     stats.an_move_count = 0;
7480
7481     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7482         stats.hint = cpstats->move_name;
7483         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7484         stats.an_move_count = cpstats->nr_moves;
7485     }
7486
7487     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7488
7489     SetProgramStats( &stats );
7490 }
7491
7492 void
7493 ClearEngineOutputPane (int which)
7494 {
7495     static FrontEndProgramStats dummyStats;
7496     dummyStats.which = which;
7497     dummyStats.pv = "#";
7498     SetProgramStats( &dummyStats );
7499 }
7500
7501 #define MAXPLAYERS 500
7502
7503 char *
7504 TourneyStandings (int display)
7505 {
7506     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7507     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7508     char result, *p, *names[MAXPLAYERS];
7509
7510     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7511         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7512     names[0] = p = strdup(appData.participants);
7513     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7514
7515     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7516
7517     while(result = appData.results[nr]) {
7518         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7519         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7520         wScore = bScore = 0;
7521         switch(result) {
7522           case '+': wScore = 2; break;
7523           case '-': bScore = 2; break;
7524           case '=': wScore = bScore = 1; break;
7525           case ' ':
7526           case '*': return strdup("busy"); // tourney not finished
7527         }
7528         score[w] += wScore;
7529         score[b] += bScore;
7530         games[w]++;
7531         games[b]++;
7532         nr++;
7533     }
7534     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7535     for(w=0; w<nPlayers; w++) {
7536         bScore = -1;
7537         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7538         ranking[w] = b; points[w] = bScore; score[b] = -2;
7539     }
7540     p = malloc(nPlayers*34+1);
7541     for(w=0; w<nPlayers && w<display; w++)
7542         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7543     free(names[0]);
7544     return p;
7545 }
7546
7547 void
7548 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7549 {       // count all piece types
7550         int p, f, r;
7551         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7552         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7553         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7554                 p = board[r][f];
7555                 pCnt[p]++;
7556                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7557                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7558                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7559                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7560                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7561                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7562         }
7563 }
7564
7565 int
7566 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7567 {
7568         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7569         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7570
7571         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7572         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7573         if(myPawns == 2 && nMine == 3) // KPP
7574             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7575         if(myPawns == 1 && nMine == 2) // KP
7576             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7577         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7578             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7579         if(myPawns) return FALSE;
7580         if(pCnt[WhiteRook+side])
7581             return pCnt[BlackRook-side] ||
7582                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7583                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7584                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7585         if(pCnt[WhiteCannon+side]) {
7586             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7587             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7588         }
7589         if(pCnt[WhiteKnight+side])
7590             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7591         return FALSE;
7592 }
7593
7594 int
7595 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7596 {
7597         VariantClass v = gameInfo.variant;
7598
7599         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7600         if(v == VariantShatranj) return TRUE; // always winnable through baring
7601         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7602         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7603
7604         if(v == VariantXiangqi) {
7605                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7606
7607                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7608                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7609                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7610                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7611                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7612                 if(stale) // we have at least one last-rank P plus perhaps C
7613                     return majors // KPKX
7614                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7615                 else // KCA*E*
7616                     return pCnt[WhiteFerz+side] // KCAK
7617                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7618                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7619                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7620
7621         } else if(v == VariantKnightmate) {
7622                 if(nMine == 1) return FALSE;
7623                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7624         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7625                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7626
7627                 if(nMine == 1) return FALSE; // bare King
7628                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7629                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7630                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7631                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7632                 if(pCnt[WhiteKnight+side])
7633                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7634                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7635                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7636                 if(nBishops)
7637                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7638                 if(pCnt[WhiteAlfil+side])
7639                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7640                 if(pCnt[WhiteWazir+side])
7641                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7642         }
7643
7644         return TRUE;
7645 }
7646
7647 int
7648 CompareWithRights (Board b1, Board b2)
7649 {
7650     int rights = 0;
7651     if(!CompareBoards(b1, b2)) return FALSE;
7652     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7653     /* compare castling rights */
7654     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7655            rights++; /* King lost rights, while rook still had them */
7656     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7657         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7658            rights++; /* but at least one rook lost them */
7659     }
7660     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7661            rights++;
7662     if( b1[CASTLING][5] != NoRights ) {
7663         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7664            rights++;
7665     }
7666     return rights == 0;
7667 }
7668
7669 int
7670 Adjudicate (ChessProgramState *cps)
7671 {       // [HGM] some adjudications useful with buggy engines
7672         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7673         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7674         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7675         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7676         int k, drop, count = 0; static int bare = 1;
7677         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7678         Boolean canAdjudicate = !appData.icsActive;
7679
7680         // most tests only when we understand the game, i.e. legality-checking on
7681             if( appData.testLegality )
7682             {   /* [HGM] Some more adjudications for obstinate engines */
7683                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7684                 static int moveCount = 6;
7685                 ChessMove result;
7686                 char *reason = NULL;
7687
7688                 /* Count what is on board. */
7689                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7690
7691                 /* Some material-based adjudications that have to be made before stalemate test */
7692                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7693                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7694                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7695                      if(canAdjudicate && appData.checkMates) {
7696                          if(engineOpponent)
7697                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7698                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7699                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7700                          return 1;
7701                      }
7702                 }
7703
7704                 /* Bare King in Shatranj (loses) or Losers (wins) */
7705                 if( nrW == 1 || nrB == 1) {
7706                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7707                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7708                      if(canAdjudicate && appData.checkMates) {
7709                          if(engineOpponent)
7710                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7711                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7712                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7713                          return 1;
7714                      }
7715                   } else
7716                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7717                   {    /* bare King */
7718                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7719                         if(canAdjudicate && appData.checkMates) {
7720                             /* but only adjudicate if adjudication enabled */
7721                             if(engineOpponent)
7722                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7723                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7724                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7725                             return 1;
7726                         }
7727                   }
7728                 } else bare = 1;
7729
7730
7731             // don't wait for engine to announce game end if we can judge ourselves
7732             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7733               case MT_CHECK:
7734                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7735                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7736                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7737                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7738                             checkCnt++;
7739                         if(checkCnt >= 2) {
7740                             reason = "Xboard adjudication: 3rd check";
7741                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7742                             break;
7743                         }
7744                     }
7745                 }
7746               case MT_NONE:
7747               default:
7748                 break;
7749               case MT_STALEMATE:
7750               case MT_STAINMATE:
7751                 reason = "Xboard adjudication: Stalemate";
7752                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7753                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7754                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7755                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7756                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7757                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7758                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7759                                                                         EP_CHECKMATE : EP_WINS);
7760                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7761                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7762                 }
7763                 break;
7764               case MT_CHECKMATE:
7765                 reason = "Xboard adjudication: Checkmate";
7766                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7767                 if(gameInfo.variant == VariantShogi) {
7768                     if(forwardMostMove > backwardMostMove
7769                        && moveList[forwardMostMove-1][1] == '@'
7770                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7771                         reason = "XBoard adjudication: pawn-drop mate";
7772                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7773                     }
7774                 }
7775                 break;
7776             }
7777
7778                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7779                     case EP_STALEMATE:
7780                         result = GameIsDrawn; break;
7781                     case EP_CHECKMATE:
7782                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7783                     case EP_WINS:
7784                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7785                     default:
7786                         result = EndOfFile;
7787                 }
7788                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7789                     if(engineOpponent)
7790                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7791                     GameEnds( result, reason, GE_XBOARD );
7792                     return 1;
7793                 }
7794
7795                 /* Next absolutely insufficient mating material. */
7796                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7797                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7798                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7799
7800                      /* always flag draws, for judging claims */
7801                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7802
7803                      if(canAdjudicate && appData.materialDraws) {
7804                          /* but only adjudicate them if adjudication enabled */
7805                          if(engineOpponent) {
7806                            SendToProgram("force\n", engineOpponent); // suppress reply
7807                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7808                          }
7809                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7810                          return 1;
7811                      }
7812                 }
7813
7814                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7815                 if(gameInfo.variant == VariantXiangqi ?
7816                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7817                  : nrW + nrB == 4 &&
7818                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7819                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7820                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7821                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7822                    ) ) {
7823                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7824                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7825                           if(engineOpponent) {
7826                             SendToProgram("force\n", engineOpponent); // suppress reply
7827                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7828                           }
7829                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7830                           return 1;
7831                      }
7832                 } else moveCount = 6;
7833             }
7834
7835         // Repetition draws and 50-move rule can be applied independently of legality testing
7836
7837                 /* Check for rep-draws */
7838                 count = 0;
7839                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7840                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7841                 for(k = forwardMostMove-2;
7842                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7843                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7844                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7845                     k-=2)
7846                 {   int rights=0;
7847                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7848                         /* compare castling rights */
7849                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7850                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7851                                 rights++; /* King lost rights, while rook still had them */
7852                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7853                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7854                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7855                                    rights++; /* but at least one rook lost them */
7856                         }
7857                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7858                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7859                                 rights++;
7860                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7861                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7862                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7863                                    rights++;
7864                         }
7865                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7866                             && appData.drawRepeats > 1) {
7867                              /* adjudicate after user-specified nr of repeats */
7868                              int result = GameIsDrawn;
7869                              char *details = "XBoard adjudication: repetition draw";
7870                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7871                                 // [HGM] xiangqi: check for forbidden perpetuals
7872                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7873                                 for(m=forwardMostMove; m>k; m-=2) {
7874                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7875                                         ourPerpetual = 0; // the current mover did not always check
7876                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7877                                         hisPerpetual = 0; // the opponent did not always check
7878                                 }
7879                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7880                                                                         ourPerpetual, hisPerpetual);
7881                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7882                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7883                                     details = "Xboard adjudication: perpetual checking";
7884                                 } else
7885                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7886                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7887                                 } else
7888                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7889                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7890                                         result = BlackWins;
7891                                         details = "Xboard adjudication: repetition";
7892                                     }
7893                                 } else // it must be XQ
7894                                 // Now check for perpetual chases
7895                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7896                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7897                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7898                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7899                                         static char resdet[MSG_SIZ];
7900                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7901                                         details = resdet;
7902                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7903                                     } else
7904                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7905                                         break; // Abort repetition-checking loop.
7906                                 }
7907                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7908                              }
7909                              if(engineOpponent) {
7910                                SendToProgram("force\n", engineOpponent); // suppress reply
7911                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7912                              }
7913                              GameEnds( result, details, GE_XBOARD );
7914                              return 1;
7915                         }
7916                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7917                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7918                     }
7919                 }
7920
7921                 /* Now we test for 50-move draws. Determine ply count */
7922                 count = forwardMostMove;
7923                 /* look for last irreversble move */
7924                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7925                     count--;
7926                 /* if we hit starting position, add initial plies */
7927                 if( count == backwardMostMove )
7928                     count -= initialRulePlies;
7929                 count = forwardMostMove - count;
7930                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7931                         // adjust reversible move counter for checks in Xiangqi
7932                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7933                         if(i < backwardMostMove) i = backwardMostMove;
7934                         while(i <= forwardMostMove) {
7935                                 lastCheck = inCheck; // check evasion does not count
7936                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7937                                 if(inCheck || lastCheck) count--; // check does not count
7938                                 i++;
7939                         }
7940                 }
7941                 if( count >= 100)
7942                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7943                          /* this is used to judge if draw claims are legal */
7944                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7945                          if(engineOpponent) {
7946                            SendToProgram("force\n", engineOpponent); // suppress reply
7947                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7948                          }
7949                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7950                          return 1;
7951                 }
7952
7953                 /* if draw offer is pending, treat it as a draw claim
7954                  * when draw condition present, to allow engines a way to
7955                  * claim draws before making their move to avoid a race
7956                  * condition occurring after their move
7957                  */
7958                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7959                          char *p = NULL;
7960                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7961                              p = "Draw claim: 50-move rule";
7962                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7963                              p = "Draw claim: 3-fold repetition";
7964                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7965                              p = "Draw claim: insufficient mating material";
7966                          if( p != NULL && canAdjudicate) {
7967                              if(engineOpponent) {
7968                                SendToProgram("force\n", engineOpponent); // suppress reply
7969                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7970                              }
7971                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7972                              return 1;
7973                          }
7974                 }
7975
7976                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7977                     if(engineOpponent) {
7978                       SendToProgram("force\n", engineOpponent); // suppress reply
7979                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7980                     }
7981                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7982                     return 1;
7983                 }
7984         return 0;
7985 }
7986
7987 char *
7988 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7989 {   // [HGM] book: this routine intercepts moves to simulate book replies
7990     char *bookHit = NULL;
7991
7992     //first determine if the incoming move brings opponent into his book
7993     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7994         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7995     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7996     if(bookHit != NULL && !cps->bookSuspend) {
7997         // make sure opponent is not going to reply after receiving move to book position
7998         SendToProgram("force\n", cps);
7999         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8000     }
8001     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8002     // now arrange restart after book miss
8003     if(bookHit) {
8004         // after a book hit we never send 'go', and the code after the call to this routine
8005         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8006         char buf[MSG_SIZ], *move = bookHit;
8007         if(cps->useSAN) {
8008             int fromX, fromY, toX, toY;
8009             char promoChar;
8010             ChessMove moveType;
8011             move = buf + 30;
8012             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8013                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8014                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8015                                     PosFlags(forwardMostMove),
8016                                     fromY, fromX, toY, toX, promoChar, move);
8017             } else {
8018                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8019                 bookHit = NULL;
8020             }
8021         }
8022         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8023         SendToProgram(buf, cps);
8024         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8025     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8026         SendToProgram("go\n", cps);
8027         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8028     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8029         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8030             SendToProgram("go\n", cps);
8031         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8032     }
8033     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8034 }
8035
8036 int
8037 LoadError (char *errmess, ChessProgramState *cps)
8038 {   // unloads engine and switches back to -ncp mode if it was first
8039     if(cps->initDone) return FALSE;
8040     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8041     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8042     cps->pr = NoProc;
8043     if(cps == &first) {
8044         appData.noChessProgram = TRUE;
8045         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8046         gameMode = BeginningOfGame; ModeHighlight();
8047         SetNCPMode();
8048     }
8049     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8050     DisplayMessage("", ""); // erase waiting message
8051     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8052     return TRUE;
8053 }
8054
8055 char *savedMessage;
8056 ChessProgramState *savedState;
8057 void
8058 DeferredBookMove (void)
8059 {
8060         if(savedState->lastPing != savedState->lastPong)
8061                     ScheduleDelayedEvent(DeferredBookMove, 10);
8062         else
8063         HandleMachineMove(savedMessage, savedState);
8064 }
8065
8066 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8067 static ChessProgramState *stalledEngine;
8068 static char stashedInputMove[MSG_SIZ];
8069
8070 void
8071 HandleMachineMove (char *message, ChessProgramState *cps)
8072 {
8073     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8074     char realname[MSG_SIZ];
8075     int fromX, fromY, toX, toY;
8076     ChessMove moveType;
8077     char promoChar;
8078     char *p, *pv=buf1;
8079     int machineWhite, oldError;
8080     char *bookHit;
8081
8082     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8083         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8084         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8085             DisplayError(_("Invalid pairing from pairing engine"), 0);
8086             return;
8087         }
8088         pairingReceived = 1;
8089         NextMatchGame();
8090         return; // Skim the pairing messages here.
8091     }
8092
8093     oldError = cps->userError; cps->userError = 0;
8094
8095 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8096     /*
8097      * Kludge to ignore BEL characters
8098      */
8099     while (*message == '\007') message++;
8100
8101     /*
8102      * [HGM] engine debug message: ignore lines starting with '#' character
8103      */
8104     if(cps->debug && *message == '#') return;
8105
8106     /*
8107      * Look for book output
8108      */
8109     if (cps == &first && bookRequested) {
8110         if (message[0] == '\t' || message[0] == ' ') {
8111             /* Part of the book output is here; append it */
8112             strcat(bookOutput, message);
8113             strcat(bookOutput, "  \n");
8114             return;
8115         } else if (bookOutput[0] != NULLCHAR) {
8116             /* All of book output has arrived; display it */
8117             char *p = bookOutput;
8118             while (*p != NULLCHAR) {
8119                 if (*p == '\t') *p = ' ';
8120                 p++;
8121             }
8122             DisplayInformation(bookOutput);
8123             bookRequested = FALSE;
8124             /* Fall through to parse the current output */
8125         }
8126     }
8127
8128     /*
8129      * Look for machine move.
8130      */
8131     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8132         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8133     {
8134         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8135             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8136             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8137             stalledEngine = cps;
8138             if(appData.ponderNextMove) { // bring opponent out of ponder
8139                 if(gameMode == TwoMachinesPlay) {
8140                     if(cps->other->pause)
8141                         PauseEngine(cps->other);
8142                     else
8143                         SendToProgram("easy\n", cps->other);
8144                 }
8145             }
8146             StopClocks();
8147             return;
8148         }
8149
8150         /* This method is only useful on engines that support ping */
8151         if (cps->lastPing != cps->lastPong) {
8152           if (gameMode == BeginningOfGame) {
8153             /* Extra move from before last new; ignore */
8154             if (appData.debugMode) {
8155                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8156             }
8157           } else {
8158             if (appData.debugMode) {
8159                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8160                         cps->which, gameMode);
8161             }
8162
8163             SendToProgram("undo\n", cps);
8164           }
8165           return;
8166         }
8167
8168         switch (gameMode) {
8169           case BeginningOfGame:
8170             /* Extra move from before last reset; ignore */
8171             if (appData.debugMode) {
8172                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8173             }
8174             return;
8175
8176           case EndOfGame:
8177           case IcsIdle:
8178           default:
8179             /* Extra move after we tried to stop.  The mode test is
8180                not a reliable way of detecting this problem, but it's
8181                the best we can do on engines that don't support ping.
8182             */
8183             if (appData.debugMode) {
8184                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8185                         cps->which, gameMode);
8186             }
8187             SendToProgram("undo\n", cps);
8188             return;
8189
8190           case MachinePlaysWhite:
8191           case IcsPlayingWhite:
8192             machineWhite = TRUE;
8193             break;
8194
8195           case MachinePlaysBlack:
8196           case IcsPlayingBlack:
8197             machineWhite = FALSE;
8198             break;
8199
8200           case TwoMachinesPlay:
8201             machineWhite = (cps->twoMachinesColor[0] == 'w');
8202             break;
8203         }
8204         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8205             if (appData.debugMode) {
8206                 fprintf(debugFP,
8207                         "Ignoring move out of turn by %s, gameMode %d"
8208                         ", forwardMost %d\n",
8209                         cps->which, gameMode, forwardMostMove);
8210             }
8211             return;
8212         }
8213
8214         if(cps->alphaRank) AlphaRank(machineMove, 4);
8215         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8216                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8217             /* Machine move could not be parsed; ignore it. */
8218           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8219                     machineMove, _(cps->which));
8220             DisplayMoveError(buf1);
8221             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8222                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8223             if (gameMode == TwoMachinesPlay) {
8224               GameEnds(machineWhite ? BlackWins : WhiteWins,
8225                        buf1, GE_XBOARD);
8226             }
8227             return;
8228         }
8229
8230         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8231         /* So we have to redo legality test with true e.p. status here,  */
8232         /* to make sure an illegal e.p. capture does not slip through,   */
8233         /* to cause a forfeit on a justified illegal-move complaint      */
8234         /* of the opponent.                                              */
8235         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8236            ChessMove moveType;
8237            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8238                              fromY, fromX, toY, toX, promoChar);
8239             if(moveType == IllegalMove) {
8240               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8241                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8242                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8243                            buf1, GE_XBOARD);
8244                 return;
8245            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8246            /* [HGM] Kludge to handle engines that send FRC-style castling
8247               when they shouldn't (like TSCP-Gothic) */
8248            switch(moveType) {
8249              case WhiteASideCastleFR:
8250              case BlackASideCastleFR:
8251                toX+=2;
8252                currentMoveString[2]++;
8253                break;
8254              case WhiteHSideCastleFR:
8255              case BlackHSideCastleFR:
8256                toX--;
8257                currentMoveString[2]--;
8258                break;
8259              default: ; // nothing to do, but suppresses warning of pedantic compilers
8260            }
8261         }
8262         hintRequested = FALSE;
8263         lastHint[0] = NULLCHAR;
8264         bookRequested = FALSE;
8265         /* Program may be pondering now */
8266         cps->maybeThinking = TRUE;
8267         if (cps->sendTime == 2) cps->sendTime = 1;
8268         if (cps->offeredDraw) cps->offeredDraw--;
8269
8270         /* [AS] Save move info*/
8271         pvInfoList[ forwardMostMove ].score = programStats.score;
8272         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8273         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8274
8275         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8276
8277         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8278         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8279             int count = 0;
8280
8281             while( count < adjudicateLossPlies ) {
8282                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8283
8284                 if( count & 1 ) {
8285                     score = -score; /* Flip score for winning side */
8286                 }
8287
8288                 if( score > adjudicateLossThreshold ) {
8289                     break;
8290                 }
8291
8292                 count++;
8293             }
8294
8295             if( count >= adjudicateLossPlies ) {
8296                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8297
8298                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8299                     "Xboard adjudication",
8300                     GE_XBOARD );
8301
8302                 return;
8303             }
8304         }
8305
8306         if(Adjudicate(cps)) {
8307             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8308             return; // [HGM] adjudicate: for all automatic game ends
8309         }
8310
8311 #if ZIPPY
8312         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8313             first.initDone) {
8314           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8315                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8316                 SendToICS("draw ");
8317                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8318           }
8319           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8320           ics_user_moved = 1;
8321           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8322                 char buf[3*MSG_SIZ];
8323
8324                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8325                         programStats.score / 100.,
8326                         programStats.depth,
8327                         programStats.time / 100.,
8328                         (unsigned int)programStats.nodes,
8329                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8330                         programStats.movelist);
8331                 SendToICS(buf);
8332 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8333           }
8334         }
8335 #endif
8336
8337         /* [AS] Clear stats for next move */
8338         ClearProgramStats();
8339         thinkOutput[0] = NULLCHAR;
8340         hiddenThinkOutputState = 0;
8341
8342         bookHit = NULL;
8343         if (gameMode == TwoMachinesPlay) {
8344             /* [HGM] relaying draw offers moved to after reception of move */
8345             /* and interpreting offer as claim if it brings draw condition */
8346             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8347                 SendToProgram("draw\n", cps->other);
8348             }
8349             if (cps->other->sendTime) {
8350                 SendTimeRemaining(cps->other,
8351                                   cps->other->twoMachinesColor[0] == 'w');
8352             }
8353             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8354             if (firstMove && !bookHit) {
8355                 firstMove = FALSE;
8356                 if (cps->other->useColors) {
8357                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8358                 }
8359                 SendToProgram("go\n", cps->other);
8360             }
8361             cps->other->maybeThinking = TRUE;
8362         }
8363
8364         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8365
8366         if (!pausing && appData.ringBellAfterMoves) {
8367             RingBell();
8368         }
8369
8370         /*
8371          * Reenable menu items that were disabled while
8372          * machine was thinking
8373          */
8374         if (gameMode != TwoMachinesPlay)
8375             SetUserThinkingEnables();
8376
8377         // [HGM] book: after book hit opponent has received move and is now in force mode
8378         // force the book reply into it, and then fake that it outputted this move by jumping
8379         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8380         if(bookHit) {
8381                 static char bookMove[MSG_SIZ]; // a bit generous?
8382
8383                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8384                 strcat(bookMove, bookHit);
8385                 message = bookMove;
8386                 cps = cps->other;
8387                 programStats.nodes = programStats.depth = programStats.time =
8388                 programStats.score = programStats.got_only_move = 0;
8389                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8390
8391                 if(cps->lastPing != cps->lastPong) {
8392                     savedMessage = message; // args for deferred call
8393                     savedState = cps;
8394                     ScheduleDelayedEvent(DeferredBookMove, 10);
8395                     return;
8396                 }
8397                 goto FakeBookMove;
8398         }
8399
8400         return;
8401     }
8402
8403     /* Set special modes for chess engines.  Later something general
8404      *  could be added here; for now there is just one kludge feature,
8405      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8406      *  when "xboard" is given as an interactive command.
8407      */
8408     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8409         cps->useSigint = FALSE;
8410         cps->useSigterm = FALSE;
8411     }
8412     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8413       ParseFeatures(message+8, cps);
8414       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8415     }
8416
8417     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8418                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8419       int dummy, s=6; char buf[MSG_SIZ];
8420       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8421       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8422       if(startedFromSetupPosition) return;
8423       ParseFEN(boards[0], &dummy, message+s);
8424       DrawPosition(TRUE, boards[0]);
8425       startedFromSetupPosition = TRUE;
8426       return;
8427     }
8428     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8429      * want this, I was asked to put it in, and obliged.
8430      */
8431     if (!strncmp(message, "setboard ", 9)) {
8432         Board initial_position;
8433
8434         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8435
8436         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8437             DisplayError(_("Bad FEN received from engine"), 0);
8438             return ;
8439         } else {
8440            Reset(TRUE, FALSE);
8441            CopyBoard(boards[0], initial_position);
8442            initialRulePlies = FENrulePlies;
8443            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8444            else gameMode = MachinePlaysBlack;
8445            DrawPosition(FALSE, boards[currentMove]);
8446         }
8447         return;
8448     }
8449
8450     /*
8451      * Look for communication commands
8452      */
8453     if (!strncmp(message, "telluser ", 9)) {
8454         if(message[9] == '\\' && message[10] == '\\')
8455             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8456         PlayTellSound();
8457         DisplayNote(message + 9);
8458         return;
8459     }
8460     if (!strncmp(message, "tellusererror ", 14)) {
8461         cps->userError = 1;
8462         if(message[14] == '\\' && message[15] == '\\')
8463             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8464         PlayTellSound();
8465         DisplayError(message + 14, 0);
8466         return;
8467     }
8468     if (!strncmp(message, "tellopponent ", 13)) {
8469       if (appData.icsActive) {
8470         if (loggedOn) {
8471           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8472           SendToICS(buf1);
8473         }
8474       } else {
8475         DisplayNote(message + 13);
8476       }
8477       return;
8478     }
8479     if (!strncmp(message, "tellothers ", 11)) {
8480       if (appData.icsActive) {
8481         if (loggedOn) {
8482           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8483           SendToICS(buf1);
8484         }
8485       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8486       return;
8487     }
8488     if (!strncmp(message, "tellall ", 8)) {
8489       if (appData.icsActive) {
8490         if (loggedOn) {
8491           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8492           SendToICS(buf1);
8493         }
8494       } else {
8495         DisplayNote(message + 8);
8496       }
8497       return;
8498     }
8499     if (strncmp(message, "warning", 7) == 0) {
8500         /* Undocumented feature, use tellusererror in new code */
8501         DisplayError(message, 0);
8502         return;
8503     }
8504     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8505         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8506         strcat(realname, " query");
8507         AskQuestion(realname, buf2, buf1, cps->pr);
8508         return;
8509     }
8510     /* Commands from the engine directly to ICS.  We don't allow these to be
8511      *  sent until we are logged on. Crafty kibitzes have been known to
8512      *  interfere with the login process.
8513      */
8514     if (loggedOn) {
8515         if (!strncmp(message, "tellics ", 8)) {
8516             SendToICS(message + 8);
8517             SendToICS("\n");
8518             return;
8519         }
8520         if (!strncmp(message, "tellicsnoalias ", 15)) {
8521             SendToICS(ics_prefix);
8522             SendToICS(message + 15);
8523             SendToICS("\n");
8524             return;
8525         }
8526         /* The following are for backward compatibility only */
8527         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8528             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8529             SendToICS(ics_prefix);
8530             SendToICS(message);
8531             SendToICS("\n");
8532             return;
8533         }
8534     }
8535     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8536         return;
8537     }
8538     /*
8539      * If the move is illegal, cancel it and redraw the board.
8540      * Also deal with other error cases.  Matching is rather loose
8541      * here to accommodate engines written before the spec.
8542      */
8543     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8544         strncmp(message, "Error", 5) == 0) {
8545         if (StrStr(message, "name") ||
8546             StrStr(message, "rating") || StrStr(message, "?") ||
8547             StrStr(message, "result") || StrStr(message, "board") ||
8548             StrStr(message, "bk") || StrStr(message, "computer") ||
8549             StrStr(message, "variant") || StrStr(message, "hint") ||
8550             StrStr(message, "random") || StrStr(message, "depth") ||
8551             StrStr(message, "accepted")) {
8552             return;
8553         }
8554         if (StrStr(message, "protover")) {
8555           /* Program is responding to input, so it's apparently done
8556              initializing, and this error message indicates it is
8557              protocol version 1.  So we don't need to wait any longer
8558              for it to initialize and send feature commands. */
8559           FeatureDone(cps, 1);
8560           cps->protocolVersion = 1;
8561           return;
8562         }
8563         cps->maybeThinking = FALSE;
8564
8565         if (StrStr(message, "draw")) {
8566             /* Program doesn't have "draw" command */
8567             cps->sendDrawOffers = 0;
8568             return;
8569         }
8570         if (cps->sendTime != 1 &&
8571             (StrStr(message, "time") || StrStr(message, "otim"))) {
8572           /* Program apparently doesn't have "time" or "otim" command */
8573           cps->sendTime = 0;
8574           return;
8575         }
8576         if (StrStr(message, "analyze")) {
8577             cps->analysisSupport = FALSE;
8578             cps->analyzing = FALSE;
8579 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8580             EditGameEvent(); // [HGM] try to preserve loaded game
8581             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8582             DisplayError(buf2, 0);
8583             return;
8584         }
8585         if (StrStr(message, "(no matching move)st")) {
8586           /* Special kludge for GNU Chess 4 only */
8587           cps->stKludge = TRUE;
8588           SendTimeControl(cps, movesPerSession, timeControl,
8589                           timeIncrement, appData.searchDepth,
8590                           searchTime);
8591           return;
8592         }
8593         if (StrStr(message, "(no matching move)sd")) {
8594           /* Special kludge for GNU Chess 4 only */
8595           cps->sdKludge = TRUE;
8596           SendTimeControl(cps, movesPerSession, timeControl,
8597                           timeIncrement, appData.searchDepth,
8598                           searchTime);
8599           return;
8600         }
8601         if (!StrStr(message, "llegal")) {
8602             return;
8603         }
8604         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8605             gameMode == IcsIdle) return;
8606         if (forwardMostMove <= backwardMostMove) return;
8607         if (pausing) PauseEvent();
8608       if(appData.forceIllegal) {
8609             // [HGM] illegal: machine refused move; force position after move into it
8610           SendToProgram("force\n", cps);
8611           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8612                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8613                 // when black is to move, while there might be nothing on a2 or black
8614                 // might already have the move. So send the board as if white has the move.
8615                 // But first we must change the stm of the engine, as it refused the last move
8616                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8617                 if(WhiteOnMove(forwardMostMove)) {
8618                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8619                     SendBoard(cps, forwardMostMove); // kludgeless board
8620                 } else {
8621                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8622                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8623                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8624                 }
8625           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8626             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8627                  gameMode == TwoMachinesPlay)
8628               SendToProgram("go\n", cps);
8629             return;
8630       } else
8631         if (gameMode == PlayFromGameFile) {
8632             /* Stop reading this game file */
8633             gameMode = EditGame;
8634             ModeHighlight();
8635         }
8636         /* [HGM] illegal-move claim should forfeit game when Xboard */
8637         /* only passes fully legal moves                            */
8638         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8639             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8640                                 "False illegal-move claim", GE_XBOARD );
8641             return; // do not take back move we tested as valid
8642         }
8643         currentMove = forwardMostMove-1;
8644         DisplayMove(currentMove-1); /* before DisplayMoveError */
8645         SwitchClocks(forwardMostMove-1); // [HGM] race
8646         DisplayBothClocks();
8647         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8648                 parseList[currentMove], _(cps->which));
8649         DisplayMoveError(buf1);
8650         DrawPosition(FALSE, boards[currentMove]);
8651
8652         SetUserThinkingEnables();
8653         return;
8654     }
8655     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8656         /* Program has a broken "time" command that
8657            outputs a string not ending in newline.
8658            Don't use it. */
8659         cps->sendTime = 0;
8660     }
8661
8662     /*
8663      * If chess program startup fails, exit with an error message.
8664      * Attempts to recover here are futile. [HGM] Well, we try anyway
8665      */
8666     if ((StrStr(message, "unknown host") != NULL)
8667         || (StrStr(message, "No remote directory") != NULL)
8668         || (StrStr(message, "not found") != NULL)
8669         || (StrStr(message, "No such file") != NULL)
8670         || (StrStr(message, "can't alloc") != NULL)
8671         || (StrStr(message, "Permission denied") != NULL)) {
8672
8673         cps->maybeThinking = FALSE;
8674         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8675                 _(cps->which), cps->program, cps->host, message);
8676         RemoveInputSource(cps->isr);
8677         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8678             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8679             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8680         }
8681         return;
8682     }
8683
8684     /*
8685      * Look for hint output
8686      */
8687     if (sscanf(message, "Hint: %s", buf1) == 1) {
8688         if (cps == &first && hintRequested) {
8689             hintRequested = FALSE;
8690             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8691                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8692                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8693                                     PosFlags(forwardMostMove),
8694                                     fromY, fromX, toY, toX, promoChar, buf1);
8695                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8696                 DisplayInformation(buf2);
8697             } else {
8698                 /* Hint move could not be parsed!? */
8699               snprintf(buf2, sizeof(buf2),
8700                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8701                         buf1, _(cps->which));
8702                 DisplayError(buf2, 0);
8703             }
8704         } else {
8705           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8706         }
8707         return;
8708     }
8709
8710     /*
8711      * Ignore other messages if game is not in progress
8712      */
8713     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8714         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8715
8716     /*
8717      * look for win, lose, draw, or draw offer
8718      */
8719     if (strncmp(message, "1-0", 3) == 0) {
8720         char *p, *q, *r = "";
8721         p = strchr(message, '{');
8722         if (p) {
8723             q = strchr(p, '}');
8724             if (q) {
8725                 *q = NULLCHAR;
8726                 r = p + 1;
8727             }
8728         }
8729         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8730         return;
8731     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
8742         if (strcmp(r, "Black resigns") == 0) {
8743             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8744             return;
8745         }
8746         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8747         return;
8748     } else if (strncmp(message, "1/2", 3) == 0) {
8749         char *p, *q, *r = "";
8750         p = strchr(message, '{');
8751         if (p) {
8752             q = strchr(p, '}');
8753             if (q) {
8754                 *q = NULLCHAR;
8755                 r = p + 1;
8756             }
8757         }
8758
8759         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8760         return;
8761
8762     } else if (strncmp(message, "White resign", 12) == 0) {
8763         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8764         return;
8765     } else if (strncmp(message, "Black resign", 12) == 0) {
8766         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8767         return;
8768     } else if (strncmp(message, "White matches", 13) == 0 ||
8769                strncmp(message, "Black matches", 13) == 0   ) {
8770         /* [HGM] ignore GNUShogi noises */
8771         return;
8772     } else if (strncmp(message, "White", 5) == 0 &&
8773                message[5] != '(' &&
8774                StrStr(message, "Black") == NULL) {
8775         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8776         return;
8777     } else if (strncmp(message, "Black", 5) == 0 &&
8778                message[5] != '(') {
8779         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8780         return;
8781     } else if (strcmp(message, "resign") == 0 ||
8782                strcmp(message, "computer resigns") == 0) {
8783         switch (gameMode) {
8784           case MachinePlaysBlack:
8785           case IcsPlayingBlack:
8786             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8787             break;
8788           case MachinePlaysWhite:
8789           case IcsPlayingWhite:
8790             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8791             break;
8792           case TwoMachinesPlay:
8793             if (cps->twoMachinesColor[0] == 'w')
8794               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8795             else
8796               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8797             break;
8798           default:
8799             /* can't happen */
8800             break;
8801         }
8802         return;
8803     } else if (strncmp(message, "opponent mates", 14) == 0) {
8804         switch (gameMode) {
8805           case MachinePlaysBlack:
8806           case IcsPlayingBlack:
8807             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8808             break;
8809           case MachinePlaysWhite:
8810           case IcsPlayingWhite:
8811             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8812             break;
8813           case TwoMachinesPlay:
8814             if (cps->twoMachinesColor[0] == 'w')
8815               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8816             else
8817               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8818             break;
8819           default:
8820             /* can't happen */
8821             break;
8822         }
8823         return;
8824     } else if (strncmp(message, "computer mates", 14) == 0) {
8825         switch (gameMode) {
8826           case MachinePlaysBlack:
8827           case IcsPlayingBlack:
8828             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8829             break;
8830           case MachinePlaysWhite:
8831           case IcsPlayingWhite:
8832             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8833             break;
8834           case TwoMachinesPlay:
8835             if (cps->twoMachinesColor[0] == 'w')
8836               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8837             else
8838               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8839             break;
8840           default:
8841             /* can't happen */
8842             break;
8843         }
8844         return;
8845     } else if (strncmp(message, "checkmate", 9) == 0) {
8846         if (WhiteOnMove(forwardMostMove)) {
8847             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8848         } else {
8849             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8850         }
8851         return;
8852     } else if (strstr(message, "Draw") != NULL ||
8853                strstr(message, "game is a draw") != NULL) {
8854         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8855         return;
8856     } else if (strstr(message, "offer") != NULL &&
8857                strstr(message, "draw") != NULL) {
8858 #if ZIPPY
8859         if (appData.zippyPlay && first.initDone) {
8860             /* Relay offer to ICS */
8861             SendToICS(ics_prefix);
8862             SendToICS("draw\n");
8863         }
8864 #endif
8865         cps->offeredDraw = 2; /* valid until this engine moves twice */
8866         if (gameMode == TwoMachinesPlay) {
8867             if (cps->other->offeredDraw) {
8868                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8869             /* [HGM] in two-machine mode we delay relaying draw offer      */
8870             /* until after we also have move, to see if it is really claim */
8871             }
8872         } else if (gameMode == MachinePlaysWhite ||
8873                    gameMode == MachinePlaysBlack) {
8874           if (userOfferedDraw) {
8875             DisplayInformation(_("Machine accepts your draw offer"));
8876             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8877           } else {
8878             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8879           }
8880         }
8881     }
8882
8883
8884     /*
8885      * Look for thinking output
8886      */
8887     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8888           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8889                                 ) {
8890         int plylev, mvleft, mvtot, curscore, time;
8891         char mvname[MOVE_LEN];
8892         u64 nodes; // [DM]
8893         char plyext;
8894         int ignore = FALSE;
8895         int prefixHint = FALSE;
8896         mvname[0] = NULLCHAR;
8897
8898         switch (gameMode) {
8899           case MachinePlaysBlack:
8900           case IcsPlayingBlack:
8901             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8902             break;
8903           case MachinePlaysWhite:
8904           case IcsPlayingWhite:
8905             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8906             break;
8907           case AnalyzeMode:
8908           case AnalyzeFile:
8909             break;
8910           case IcsObserving: /* [DM] icsEngineAnalyze */
8911             if (!appData.icsEngineAnalyze) ignore = TRUE;
8912             break;
8913           case TwoMachinesPlay:
8914             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8915                 ignore = TRUE;
8916             }
8917             break;
8918           default:
8919             ignore = TRUE;
8920             break;
8921         }
8922
8923         if (!ignore) {
8924             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8925             buf1[0] = NULLCHAR;
8926             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8927                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8928
8929                 if (plyext != ' ' && plyext != '\t') {
8930                     time *= 100;
8931                 }
8932
8933                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8934                 if( cps->scoreIsAbsolute &&
8935                     ( gameMode == MachinePlaysBlack ||
8936                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8937                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8938                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8939                      !WhiteOnMove(currentMove)
8940                     ) )
8941                 {
8942                     curscore = -curscore;
8943                 }
8944
8945                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8946
8947                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8948                         char buf[MSG_SIZ];
8949                         FILE *f;
8950                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8951                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8952                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8953                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8954                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8955                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8956                                 fclose(f);
8957                         } else DisplayError(_("failed writing PV"), 0);
8958                 }
8959
8960                 tempStats.depth = plylev;
8961                 tempStats.nodes = nodes;
8962                 tempStats.time = time;
8963                 tempStats.score = curscore;
8964                 tempStats.got_only_move = 0;
8965
8966                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8967                         int ticklen;
8968
8969                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8970                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8971                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8972                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8973                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8974                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8975                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8976                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8977                 }
8978
8979                 /* Buffer overflow protection */
8980                 if (pv[0] != NULLCHAR) {
8981                     if (strlen(pv) >= sizeof(tempStats.movelist)
8982                         && appData.debugMode) {
8983                         fprintf(debugFP,
8984                                 "PV is too long; using the first %u bytes.\n",
8985                                 (unsigned) sizeof(tempStats.movelist) - 1);
8986                     }
8987
8988                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8989                 } else {
8990                     sprintf(tempStats.movelist, " no PV\n");
8991                 }
8992
8993                 if (tempStats.seen_stat) {
8994                     tempStats.ok_to_send = 1;
8995                 }
8996
8997                 if (strchr(tempStats.movelist, '(') != NULL) {
8998                     tempStats.line_is_book = 1;
8999                     tempStats.nr_moves = 0;
9000                     tempStats.moves_left = 0;
9001                 } else {
9002                     tempStats.line_is_book = 0;
9003                 }
9004
9005                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9006                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9007
9008                 SendProgramStatsToFrontend( cps, &tempStats );
9009
9010                 /*
9011                     [AS] Protect the thinkOutput buffer from overflow... this
9012                     is only useful if buf1 hasn't overflowed first!
9013                 */
9014                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9015                          plylev,
9016                          (gameMode == TwoMachinesPlay ?
9017                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9018                          ((double) curscore) / 100.0,
9019                          prefixHint ? lastHint : "",
9020                          prefixHint ? " " : "" );
9021
9022                 if( buf1[0] != NULLCHAR ) {
9023                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9024
9025                     if( strlen(pv) > max_len ) {
9026                         if( appData.debugMode) {
9027                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9028                         }
9029                         pv[max_len+1] = '\0';
9030                     }
9031
9032                     strcat( thinkOutput, pv);
9033                 }
9034
9035                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9036                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9037                     DisplayMove(currentMove - 1);
9038                 }
9039                 return;
9040
9041             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9042                 /* crafty (9.25+) says "(only move) <move>"
9043                  * if there is only 1 legal move
9044                  */
9045                 sscanf(p, "(only move) %s", buf1);
9046                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9047                 sprintf(programStats.movelist, "%s (only move)", buf1);
9048                 programStats.depth = 1;
9049                 programStats.nr_moves = 1;
9050                 programStats.moves_left = 1;
9051                 programStats.nodes = 1;
9052                 programStats.time = 1;
9053                 programStats.got_only_move = 1;
9054
9055                 /* Not really, but we also use this member to
9056                    mean "line isn't going to change" (Crafty
9057                    isn't searching, so stats won't change) */
9058                 programStats.line_is_book = 1;
9059
9060                 SendProgramStatsToFrontend( cps, &programStats );
9061
9062                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9063                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9064                     DisplayMove(currentMove - 1);
9065                 }
9066                 return;
9067             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9068                               &time, &nodes, &plylev, &mvleft,
9069                               &mvtot, mvname) >= 5) {
9070                 /* The stat01: line is from Crafty (9.29+) in response
9071                    to the "." command */
9072                 programStats.seen_stat = 1;
9073                 cps->maybeThinking = TRUE;
9074
9075                 if (programStats.got_only_move || !appData.periodicUpdates)
9076                   return;
9077
9078                 programStats.depth = plylev;
9079                 programStats.time = time;
9080                 programStats.nodes = nodes;
9081                 programStats.moves_left = mvleft;
9082                 programStats.nr_moves = mvtot;
9083                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9084                 programStats.ok_to_send = 1;
9085                 programStats.movelist[0] = '\0';
9086
9087                 SendProgramStatsToFrontend( cps, &programStats );
9088
9089                 return;
9090
9091             } else if (strncmp(message,"++",2) == 0) {
9092                 /* Crafty 9.29+ outputs this */
9093                 programStats.got_fail = 2;
9094                 return;
9095
9096             } else if (strncmp(message,"--",2) == 0) {
9097                 /* Crafty 9.29+ outputs this */
9098                 programStats.got_fail = 1;
9099                 return;
9100
9101             } else if (thinkOutput[0] != NULLCHAR &&
9102                        strncmp(message, "    ", 4) == 0) {
9103                 unsigned message_len;
9104
9105                 p = message;
9106                 while (*p && *p == ' ') p++;
9107
9108                 message_len = strlen( p );
9109
9110                 /* [AS] Avoid buffer overflow */
9111                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9112                     strcat(thinkOutput, " ");
9113                     strcat(thinkOutput, p);
9114                 }
9115
9116                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9117                     strcat(programStats.movelist, " ");
9118                     strcat(programStats.movelist, p);
9119                 }
9120
9121                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9122                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9123                     DisplayMove(currentMove - 1);
9124                 }
9125                 return;
9126             }
9127         }
9128         else {
9129             buf1[0] = NULLCHAR;
9130
9131             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9132                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9133             {
9134                 ChessProgramStats cpstats;
9135
9136                 if (plyext != ' ' && plyext != '\t') {
9137                     time *= 100;
9138                 }
9139
9140                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9141                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9142                     curscore = -curscore;
9143                 }
9144
9145                 cpstats.depth = plylev;
9146                 cpstats.nodes = nodes;
9147                 cpstats.time = time;
9148                 cpstats.score = curscore;
9149                 cpstats.got_only_move = 0;
9150                 cpstats.movelist[0] = '\0';
9151
9152                 if (buf1[0] != NULLCHAR) {
9153                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9154                 }
9155
9156                 cpstats.ok_to_send = 0;
9157                 cpstats.line_is_book = 0;
9158                 cpstats.nr_moves = 0;
9159                 cpstats.moves_left = 0;
9160
9161                 SendProgramStatsToFrontend( cps, &cpstats );
9162             }
9163         }
9164     }
9165 }
9166
9167
9168 /* Parse a game score from the character string "game", and
9169    record it as the history of the current game.  The game
9170    score is NOT assumed to start from the standard position.
9171    The display is not updated in any way.
9172    */
9173 void
9174 ParseGameHistory (char *game)
9175 {
9176     ChessMove moveType;
9177     int fromX, fromY, toX, toY, boardIndex;
9178     char promoChar;
9179     char *p, *q;
9180     char buf[MSG_SIZ];
9181
9182     if (appData.debugMode)
9183       fprintf(debugFP, "Parsing game history: %s\n", game);
9184
9185     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9186     gameInfo.site = StrSave(appData.icsHost);
9187     gameInfo.date = PGNDate();
9188     gameInfo.round = StrSave("-");
9189
9190     /* Parse out names of players */
9191     while (*game == ' ') game++;
9192     p = buf;
9193     while (*game != ' ') *p++ = *game++;
9194     *p = NULLCHAR;
9195     gameInfo.white = StrSave(buf);
9196     while (*game == ' ') game++;
9197     p = buf;
9198     while (*game != ' ' && *game != '\n') *p++ = *game++;
9199     *p = NULLCHAR;
9200     gameInfo.black = StrSave(buf);
9201
9202     /* Parse moves */
9203     boardIndex = blackPlaysFirst ? 1 : 0;
9204     yynewstr(game);
9205     for (;;) {
9206         yyboardindex = boardIndex;
9207         moveType = (ChessMove) Myylex();
9208         switch (moveType) {
9209           case IllegalMove:             /* maybe suicide chess, etc. */
9210   if (appData.debugMode) {
9211     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9212     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9213     setbuf(debugFP, NULL);
9214   }
9215           case WhitePromotion:
9216           case BlackPromotion:
9217           case WhiteNonPromotion:
9218           case BlackNonPromotion:
9219           case NormalMove:
9220           case WhiteCapturesEnPassant:
9221           case BlackCapturesEnPassant:
9222           case WhiteKingSideCastle:
9223           case WhiteQueenSideCastle:
9224           case BlackKingSideCastle:
9225           case BlackQueenSideCastle:
9226           case WhiteKingSideCastleWild:
9227           case WhiteQueenSideCastleWild:
9228           case BlackKingSideCastleWild:
9229           case BlackQueenSideCastleWild:
9230           /* PUSH Fabien */
9231           case WhiteHSideCastleFR:
9232           case WhiteASideCastleFR:
9233           case BlackHSideCastleFR:
9234           case BlackASideCastleFR:
9235           /* POP Fabien */
9236             fromX = currentMoveString[0] - AAA;
9237             fromY = currentMoveString[1] - ONE;
9238             toX = currentMoveString[2] - AAA;
9239             toY = currentMoveString[3] - ONE;
9240             promoChar = currentMoveString[4];
9241             break;
9242           case WhiteDrop:
9243           case BlackDrop:
9244             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9245             fromX = moveType == WhiteDrop ?
9246               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9247             (int) CharToPiece(ToLower(currentMoveString[0]));
9248             fromY = DROP_RANK;
9249             toX = currentMoveString[2] - AAA;
9250             toY = currentMoveString[3] - ONE;
9251             promoChar = NULLCHAR;
9252             break;
9253           case AmbiguousMove:
9254             /* bug? */
9255             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9256   if (appData.debugMode) {
9257     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9258     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9259     setbuf(debugFP, NULL);
9260   }
9261             DisplayError(buf, 0);
9262             return;
9263           case ImpossibleMove:
9264             /* bug? */
9265             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9266   if (appData.debugMode) {
9267     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9268     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9269     setbuf(debugFP, NULL);
9270   }
9271             DisplayError(buf, 0);
9272             return;
9273           case EndOfFile:
9274             if (boardIndex < backwardMostMove) {
9275                 /* Oops, gap.  How did that happen? */
9276                 DisplayError(_("Gap in move list"), 0);
9277                 return;
9278             }
9279             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9280             if (boardIndex > forwardMostMove) {
9281                 forwardMostMove = boardIndex;
9282             }
9283             return;
9284           case ElapsedTime:
9285             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9286                 strcat(parseList[boardIndex-1], " ");
9287                 strcat(parseList[boardIndex-1], yy_text);
9288             }
9289             continue;
9290           case Comment:
9291           case PGNTag:
9292           case NAG:
9293           default:
9294             /* ignore */
9295             continue;
9296           case WhiteWins:
9297           case BlackWins:
9298           case GameIsDrawn:
9299           case GameUnfinished:
9300             if (gameMode == IcsExamining) {
9301                 if (boardIndex < backwardMostMove) {
9302                     /* Oops, gap.  How did that happen? */
9303                     return;
9304                 }
9305                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9306                 return;
9307             }
9308             gameInfo.result = moveType;
9309             p = strchr(yy_text, '{');
9310             if (p == NULL) p = strchr(yy_text, '(');
9311             if (p == NULL) {
9312                 p = yy_text;
9313                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9314             } else {
9315                 q = strchr(p, *p == '{' ? '}' : ')');
9316                 if (q != NULL) *q = NULLCHAR;
9317                 p++;
9318             }
9319             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9320             gameInfo.resultDetails = StrSave(p);
9321             continue;
9322         }
9323         if (boardIndex >= forwardMostMove &&
9324             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9325             backwardMostMove = blackPlaysFirst ? 1 : 0;
9326             return;
9327         }
9328         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9329                                  fromY, fromX, toY, toX, promoChar,
9330                                  parseList[boardIndex]);
9331         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9332         /* currentMoveString is set as a side-effect of yylex */
9333         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9334         strcat(moveList[boardIndex], "\n");
9335         boardIndex++;
9336         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9337         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9338           case MT_NONE:
9339           case MT_STALEMATE:
9340           default:
9341             break;
9342           case MT_CHECK:
9343             if(gameInfo.variant != VariantShogi)
9344                 strcat(parseList[boardIndex - 1], "+");
9345             break;
9346           case MT_CHECKMATE:
9347           case MT_STAINMATE:
9348             strcat(parseList[boardIndex - 1], "#");
9349             break;
9350         }
9351     }
9352 }
9353
9354
9355 /* Apply a move to the given board  */
9356 void
9357 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9358 {
9359   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9360   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9361
9362     /* [HGM] compute & store e.p. status and castling rights for new position */
9363     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9364
9365       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9366       oldEP = (signed char)board[EP_STATUS];
9367       board[EP_STATUS] = EP_NONE;
9368
9369   if (fromY == DROP_RANK) {
9370         /* must be first */
9371         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9372             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9373             return;
9374         }
9375         piece = board[toY][toX] = (ChessSquare) fromX;
9376   } else {
9377       int i;
9378
9379       if( board[toY][toX] != EmptySquare )
9380            board[EP_STATUS] = EP_CAPTURE;
9381
9382       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9383            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9384                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9385       } else
9386       if( board[fromY][fromX] == WhitePawn ) {
9387            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9388                board[EP_STATUS] = EP_PAWN_MOVE;
9389            if( toY-fromY==2) {
9390                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9391                         gameInfo.variant != VariantBerolina || toX < fromX)
9392                       board[EP_STATUS] = toX | berolina;
9393                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9394                         gameInfo.variant != VariantBerolina || toX > fromX)
9395                       board[EP_STATUS] = toX;
9396            }
9397       } else
9398       if( board[fromY][fromX] == BlackPawn ) {
9399            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9400                board[EP_STATUS] = EP_PAWN_MOVE;
9401            if( toY-fromY== -2) {
9402                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9403                         gameInfo.variant != VariantBerolina || toX < fromX)
9404                       board[EP_STATUS] = toX | berolina;
9405                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9406                         gameInfo.variant != VariantBerolina || toX > fromX)
9407                       board[EP_STATUS] = toX;
9408            }
9409        }
9410
9411        for(i=0; i<nrCastlingRights; i++) {
9412            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9413               board[CASTLING][i] == toX   && castlingRank[i] == toY
9414              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9415        }
9416
9417        if(gameInfo.variant == VariantSChess) { // update virginity
9418            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9419            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9420            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9421            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9422        }
9423
9424      if (fromX == toX && fromY == toY) return;
9425
9426      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9427      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9428      if(gameInfo.variant == VariantKnightmate)
9429          king += (int) WhiteUnicorn - (int) WhiteKing;
9430
9431     /* Code added by Tord: */
9432     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9433     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9434         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9435       board[fromY][fromX] = EmptySquare;
9436       board[toY][toX] = EmptySquare;
9437       if((toX > fromX) != (piece == WhiteRook)) {
9438         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9439       } else {
9440         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9441       }
9442     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9443                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9444       board[fromY][fromX] = EmptySquare;
9445       board[toY][toX] = EmptySquare;
9446       if((toX > fromX) != (piece == BlackRook)) {
9447         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9448       } else {
9449         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9450       }
9451     /* End of code added by Tord */
9452
9453     } else if (board[fromY][fromX] == king
9454         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9455         && toY == fromY && toX > fromX+1) {
9456         board[fromY][fromX] = EmptySquare;
9457         board[toY][toX] = king;
9458         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9459         board[fromY][BOARD_RGHT-1] = EmptySquare;
9460     } else if (board[fromY][fromX] == king
9461         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9462                && toY == fromY && toX < fromX-1) {
9463         board[fromY][fromX] = EmptySquare;
9464         board[toY][toX] = king;
9465         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9466         board[fromY][BOARD_LEFT] = EmptySquare;
9467     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9468                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9469                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9470                ) {
9471         /* white pawn promotion */
9472         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9473         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9474             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9475         board[fromY][fromX] = EmptySquare;
9476     } else if ((fromY >= BOARD_HEIGHT>>1)
9477                && (toX != fromX)
9478                && gameInfo.variant != VariantXiangqi
9479                && gameInfo.variant != VariantBerolina
9480                && (board[fromY][fromX] == WhitePawn)
9481                && (board[toY][toX] == EmptySquare)) {
9482         board[fromY][fromX] = EmptySquare;
9483         board[toY][toX] = WhitePawn;
9484         captured = board[toY - 1][toX];
9485         board[toY - 1][toX] = EmptySquare;
9486     } else if ((fromY == BOARD_HEIGHT-4)
9487                && (toX == fromX)
9488                && gameInfo.variant == VariantBerolina
9489                && (board[fromY][fromX] == WhitePawn)
9490                && (board[toY][toX] == EmptySquare)) {
9491         board[fromY][fromX] = EmptySquare;
9492         board[toY][toX] = WhitePawn;
9493         if(oldEP & EP_BEROLIN_A) {
9494                 captured = board[fromY][fromX-1];
9495                 board[fromY][fromX-1] = EmptySquare;
9496         }else{  captured = board[fromY][fromX+1];
9497                 board[fromY][fromX+1] = EmptySquare;
9498         }
9499     } else if (board[fromY][fromX] == king
9500         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9501                && toY == fromY && toX > fromX+1) {
9502         board[fromY][fromX] = EmptySquare;
9503         board[toY][toX] = king;
9504         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9505         board[fromY][BOARD_RGHT-1] = EmptySquare;
9506     } else if (board[fromY][fromX] == king
9507         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9508                && toY == fromY && toX < fromX-1) {
9509         board[fromY][fromX] = EmptySquare;
9510         board[toY][toX] = king;
9511         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9512         board[fromY][BOARD_LEFT] = EmptySquare;
9513     } else if (fromY == 7 && fromX == 3
9514                && board[fromY][fromX] == BlackKing
9515                && toY == 7 && toX == 5) {
9516         board[fromY][fromX] = EmptySquare;
9517         board[toY][toX] = BlackKing;
9518         board[fromY][7] = EmptySquare;
9519         board[toY][4] = BlackRook;
9520     } else if (fromY == 7 && fromX == 3
9521                && board[fromY][fromX] == BlackKing
9522                && toY == 7 && toX == 1) {
9523         board[fromY][fromX] = EmptySquare;
9524         board[toY][toX] = BlackKing;
9525         board[fromY][0] = EmptySquare;
9526         board[toY][2] = BlackRook;
9527     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9528                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9529                && toY < promoRank && promoChar
9530                ) {
9531         /* black pawn promotion */
9532         board[toY][toX] = CharToPiece(ToLower(promoChar));
9533         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9534             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9535         board[fromY][fromX] = EmptySquare;
9536     } else if ((fromY < BOARD_HEIGHT>>1)
9537                && (toX != fromX)
9538                && gameInfo.variant != VariantXiangqi
9539                && gameInfo.variant != VariantBerolina
9540                && (board[fromY][fromX] == BlackPawn)
9541                && (board[toY][toX] == EmptySquare)) {
9542         board[fromY][fromX] = EmptySquare;
9543         board[toY][toX] = BlackPawn;
9544         captured = board[toY + 1][toX];
9545         board[toY + 1][toX] = EmptySquare;
9546     } else if ((fromY == 3)
9547                && (toX == fromX)
9548                && gameInfo.variant == VariantBerolina
9549                && (board[fromY][fromX] == BlackPawn)
9550                && (board[toY][toX] == EmptySquare)) {
9551         board[fromY][fromX] = EmptySquare;
9552         board[toY][toX] = BlackPawn;
9553         if(oldEP & EP_BEROLIN_A) {
9554                 captured = board[fromY][fromX-1];
9555                 board[fromY][fromX-1] = EmptySquare;
9556         }else{  captured = board[fromY][fromX+1];
9557                 board[fromY][fromX+1] = EmptySquare;
9558         }
9559     } else {
9560         board[toY][toX] = board[fromY][fromX];
9561         board[fromY][fromX] = EmptySquare;
9562     }
9563   }
9564
9565     if (gameInfo.holdingsWidth != 0) {
9566
9567       /* !!A lot more code needs to be written to support holdings  */
9568       /* [HGM] OK, so I have written it. Holdings are stored in the */
9569       /* penultimate board files, so they are automaticlly stored   */
9570       /* in the game history.                                       */
9571       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9572                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9573         /* Delete from holdings, by decreasing count */
9574         /* and erasing image if necessary            */
9575         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9576         if(p < (int) BlackPawn) { /* white drop */
9577              p -= (int)WhitePawn;
9578                  p = PieceToNumber((ChessSquare)p);
9579              if(p >= gameInfo.holdingsSize) p = 0;
9580              if(--board[p][BOARD_WIDTH-2] <= 0)
9581                   board[p][BOARD_WIDTH-1] = EmptySquare;
9582              if((int)board[p][BOARD_WIDTH-2] < 0)
9583                         board[p][BOARD_WIDTH-2] = 0;
9584         } else {                  /* black drop */
9585              p -= (int)BlackPawn;
9586                  p = PieceToNumber((ChessSquare)p);
9587              if(p >= gameInfo.holdingsSize) p = 0;
9588              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9589                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9590              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9591                         board[BOARD_HEIGHT-1-p][1] = 0;
9592         }
9593       }
9594       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9595           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9596         /* [HGM] holdings: Add to holdings, if holdings exist */
9597         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9598                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9599                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9600         }
9601         p = (int) captured;
9602         if (p >= (int) BlackPawn) {
9603           p -= (int)BlackPawn;
9604           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9605                   /* in Shogi restore piece to its original  first */
9606                   captured = (ChessSquare) (DEMOTED captured);
9607                   p = DEMOTED p;
9608           }
9609           p = PieceToNumber((ChessSquare)p);
9610           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9611           board[p][BOARD_WIDTH-2]++;
9612           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9613         } else {
9614           p -= (int)WhitePawn;
9615           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9616                   captured = (ChessSquare) (DEMOTED captured);
9617                   p = DEMOTED p;
9618           }
9619           p = PieceToNumber((ChessSquare)p);
9620           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9621           board[BOARD_HEIGHT-1-p][1]++;
9622           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9623         }
9624       }
9625     } else if (gameInfo.variant == VariantAtomic) {
9626       if (captured != EmptySquare) {
9627         int y, x;
9628         for (y = toY-1; y <= toY+1; y++) {
9629           for (x = toX-1; x <= toX+1; x++) {
9630             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9631                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9632               board[y][x] = EmptySquare;
9633             }
9634           }
9635         }
9636         board[toY][toX] = EmptySquare;
9637       }
9638     }
9639     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9640         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9641     } else
9642     if(promoChar == '+') {
9643         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9644         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9645     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9646         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9647         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9648            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9649         board[toY][toX] = newPiece;
9650     }
9651     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9652                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9653         // [HGM] superchess: take promotion piece out of holdings
9654         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9655         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9656             if(!--board[k][BOARD_WIDTH-2])
9657                 board[k][BOARD_WIDTH-1] = EmptySquare;
9658         } else {
9659             if(!--board[BOARD_HEIGHT-1-k][1])
9660                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9661         }
9662     }
9663
9664 }
9665
9666 /* Updates forwardMostMove */
9667 void
9668 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9669 {
9670 //    forwardMostMove++; // [HGM] bare: moved downstream
9671
9672     (void) CoordsToAlgebraic(boards[forwardMostMove],
9673                              PosFlags(forwardMostMove),
9674                              fromY, fromX, toY, toX, promoChar,
9675                              parseList[forwardMostMove]);
9676
9677     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9678         int timeLeft; static int lastLoadFlag=0; int king, piece;
9679         piece = boards[forwardMostMove][fromY][fromX];
9680         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9681         if(gameInfo.variant == VariantKnightmate)
9682             king += (int) WhiteUnicorn - (int) WhiteKing;
9683         if(forwardMostMove == 0) {
9684             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9685                 fprintf(serverMoves, "%s;", UserName());
9686             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9687                 fprintf(serverMoves, "%s;", second.tidy);
9688             fprintf(serverMoves, "%s;", first.tidy);
9689             if(gameMode == MachinePlaysWhite)
9690                 fprintf(serverMoves, "%s;", UserName());
9691             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9692                 fprintf(serverMoves, "%s;", second.tidy);
9693         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9694         lastLoadFlag = loadFlag;
9695         // print base move
9696         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9697         // print castling suffix
9698         if( toY == fromY && piece == king ) {
9699             if(toX-fromX > 1)
9700                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9701             if(fromX-toX >1)
9702                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9703         }
9704         // e.p. suffix
9705         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9706              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9707              boards[forwardMostMove][toY][toX] == EmptySquare
9708              && fromX != toX && fromY != toY)
9709                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9710         // promotion suffix
9711         if(promoChar != NULLCHAR) {
9712             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9713                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9714                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9715             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9716         }
9717         if(!loadFlag) {
9718                 char buf[MOVE_LEN*2], *p; int len;
9719             fprintf(serverMoves, "/%d/%d",
9720                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9721             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9722             else                      timeLeft = blackTimeRemaining/1000;
9723             fprintf(serverMoves, "/%d", timeLeft);
9724                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9725                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9726                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9727                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9728             fprintf(serverMoves, "/%s", buf);
9729         }
9730         fflush(serverMoves);
9731     }
9732
9733     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9734         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9735       return;
9736     }
9737     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9738     if (commentList[forwardMostMove+1] != NULL) {
9739         free(commentList[forwardMostMove+1]);
9740         commentList[forwardMostMove+1] = NULL;
9741     }
9742     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9743     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9744     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9745     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9746     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9747     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9748     adjustedClock = FALSE;
9749     gameInfo.result = GameUnfinished;
9750     if (gameInfo.resultDetails != NULL) {
9751         free(gameInfo.resultDetails);
9752         gameInfo.resultDetails = NULL;
9753     }
9754     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9755                               moveList[forwardMostMove - 1]);
9756     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9757       case MT_NONE:
9758       case MT_STALEMATE:
9759       default:
9760         break;
9761       case MT_CHECK:
9762         if(gameInfo.variant != VariantShogi)
9763             strcat(parseList[forwardMostMove - 1], "+");
9764         break;
9765       case MT_CHECKMATE:
9766       case MT_STAINMATE:
9767         strcat(parseList[forwardMostMove - 1], "#");
9768         break;
9769     }
9770
9771 }
9772
9773 /* Updates currentMove if not pausing */
9774 void
9775 ShowMove (int fromX, int fromY, int toX, int toY)
9776 {
9777     int instant = (gameMode == PlayFromGameFile) ?
9778         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9779     if(appData.noGUI) return;
9780     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9781         if (!instant) {
9782             if (forwardMostMove == currentMove + 1) {
9783                 AnimateMove(boards[forwardMostMove - 1],
9784                             fromX, fromY, toX, toY);
9785             }
9786         }
9787         currentMove = forwardMostMove;
9788     }
9789
9790     if (instant) return;
9791
9792     DisplayMove(currentMove - 1);
9793     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9794             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9795                 SetHighlights(fromX, fromY, toX, toY);
9796             }
9797     }
9798     DrawPosition(FALSE, boards[currentMove]);
9799     DisplayBothClocks();
9800     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9801 }
9802
9803 void
9804 SendEgtPath (ChessProgramState *cps)
9805 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9806         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9807
9808         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9809
9810         while(*p) {
9811             char c, *q = name+1, *r, *s;
9812
9813             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9814             while(*p && *p != ',') *q++ = *p++;
9815             *q++ = ':'; *q = 0;
9816             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9817                 strcmp(name, ",nalimov:") == 0 ) {
9818                 // take nalimov path from the menu-changeable option first, if it is defined
9819               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9820                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9821             } else
9822             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9823                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9824                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9825                 s = r = StrStr(s, ":") + 1; // beginning of path info
9826                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9827                 c = *r; *r = 0;             // temporarily null-terminate path info
9828                     *--q = 0;               // strip of trailig ':' from name
9829                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9830                 *r = c;
9831                 SendToProgram(buf,cps);     // send egtbpath command for this format
9832             }
9833             if(*p == ',') p++; // read away comma to position for next format name
9834         }
9835 }
9836
9837 void
9838 InitChessProgram (ChessProgramState *cps, int setup)
9839 /* setup needed to setup FRC opening position */
9840 {
9841     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9842     if (appData.noChessProgram) return;
9843     hintRequested = FALSE;
9844     bookRequested = FALSE;
9845
9846     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9847     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9848     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9849     if(cps->memSize) { /* [HGM] memory */
9850       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9851         SendToProgram(buf, cps);
9852     }
9853     SendEgtPath(cps); /* [HGM] EGT */
9854     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9855       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9856         SendToProgram(buf, cps);
9857     }
9858
9859     SendToProgram(cps->initString, cps);
9860     if (gameInfo.variant != VariantNormal &&
9861         gameInfo.variant != VariantLoadable
9862         /* [HGM] also send variant if board size non-standard */
9863         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9864                                             ) {
9865       char *v = VariantName(gameInfo.variant);
9866       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9867         /* [HGM] in protocol 1 we have to assume all variants valid */
9868         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9869         DisplayFatalError(buf, 0, 1);
9870         return;
9871       }
9872
9873       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9874       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9875       if( gameInfo.variant == VariantXiangqi )
9876            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9877       if( gameInfo.variant == VariantShogi )
9878            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9879       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9880            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9881       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9882           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9883            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9884       if( gameInfo.variant == VariantCourier )
9885            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9886       if( gameInfo.variant == VariantSuper )
9887            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9888       if( gameInfo.variant == VariantGreat )
9889            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9890       if( gameInfo.variant == VariantSChess )
9891            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9892       if( gameInfo.variant == VariantGrand )
9893            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9894
9895       if(overruled) {
9896         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9897                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9898            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9899            if(StrStr(cps->variants, b) == NULL) {
9900                // specific sized variant not known, check if general sizing allowed
9901                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9902                    if(StrStr(cps->variants, "boardsize") == NULL) {
9903                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9904                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9905                        DisplayFatalError(buf, 0, 1);
9906                        return;
9907                    }
9908                    /* [HGM] here we really should compare with the maximum supported board size */
9909                }
9910            }
9911       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9912       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9913       SendToProgram(buf, cps);
9914     }
9915     currentlyInitializedVariant = gameInfo.variant;
9916
9917     /* [HGM] send opening position in FRC to first engine */
9918     if(setup) {
9919           SendToProgram("force\n", cps);
9920           SendBoard(cps, 0);
9921           /* engine is now in force mode! Set flag to wake it up after first move. */
9922           setboardSpoiledMachineBlack = 1;
9923     }
9924
9925     if (cps->sendICS) {
9926       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9927       SendToProgram(buf, cps);
9928     }
9929     cps->maybeThinking = FALSE;
9930     cps->offeredDraw = 0;
9931     if (!appData.icsActive) {
9932         SendTimeControl(cps, movesPerSession, timeControl,
9933                         timeIncrement, appData.searchDepth,
9934                         searchTime);
9935     }
9936     if (appData.showThinking
9937         // [HGM] thinking: four options require thinking output to be sent
9938         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9939                                 ) {
9940         SendToProgram("post\n", cps);
9941     }
9942     SendToProgram("hard\n", cps);
9943     if (!appData.ponderNextMove) {
9944         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9945            it without being sure what state we are in first.  "hard"
9946            is not a toggle, so that one is OK.
9947          */
9948         SendToProgram("easy\n", cps);
9949     }
9950     if (cps->usePing) {
9951       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9952       SendToProgram(buf, cps);
9953     }
9954     cps->initDone = TRUE;
9955     ClearEngineOutputPane(cps == &second);
9956 }
9957
9958
9959 void
9960 ResendOptions (ChessProgramState *cps)
9961 { // send the stored value of the options
9962   int i;
9963   char buf[MSG_SIZ];
9964   Option *opt = cps->option;
9965   for(i=0; i<cps->nrOptions; i++, opt++) {
9966       switch(opt->type) {
9967         case Spin:
9968         case Slider:
9969         case CheckBox:
9970             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9971           break;
9972         case ComboBox:
9973           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9974           break;
9975         default:
9976             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9977           break;
9978         case Button:
9979         case SaveButton:
9980           continue;
9981       }
9982       SendToProgram(buf, cps);
9983   }
9984 }
9985
9986 void
9987 StartChessProgram (ChessProgramState *cps)
9988 {
9989     char buf[MSG_SIZ];
9990     int err;
9991
9992     if (appData.noChessProgram) return;
9993     cps->initDone = FALSE;
9994
9995     if (strcmp(cps->host, "localhost") == 0) {
9996         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9997     } else if (*appData.remoteShell == NULLCHAR) {
9998         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9999     } else {
10000         if (*appData.remoteUser == NULLCHAR) {
10001           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10002                     cps->program);
10003         } else {
10004           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10005                     cps->host, appData.remoteUser, cps->program);
10006         }
10007         err = StartChildProcess(buf, "", &cps->pr);
10008     }
10009
10010     if (err != 0) {
10011       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10012         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10013         if(cps != &first) return;
10014         appData.noChessProgram = TRUE;
10015         ThawUI();
10016         SetNCPMode();
10017 //      DisplayFatalError(buf, err, 1);
10018 //      cps->pr = NoProc;
10019 //      cps->isr = NULL;
10020         return;
10021     }
10022
10023     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10024     if (cps->protocolVersion > 1) {
10025       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10026       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10027         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10028         cps->comboCnt = 0;  //                and values of combo boxes
10029       }
10030       SendToProgram(buf, cps);
10031       if(cps->reload) ResendOptions(cps);
10032     } else {
10033       SendToProgram("xboard\n", cps);
10034     }
10035 }
10036
10037 void
10038 TwoMachinesEventIfReady P((void))
10039 {
10040   static int curMess = 0;
10041   if (first.lastPing != first.lastPong || !first.initDone) {
10042     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10043     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10044     return;
10045   }
10046   if (second.lastPing != second.lastPong) {
10047     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10048     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10049     return;
10050   }
10051   DisplayMessage("", ""); curMess = 0;
10052   ThawUI();
10053   TwoMachinesEvent();
10054 }
10055
10056 char *
10057 MakeName (char *template)
10058 {
10059     time_t clock;
10060     struct tm *tm;
10061     static char buf[MSG_SIZ];
10062     char *p = buf;
10063     int i;
10064
10065     clock = time((time_t *)NULL);
10066     tm = localtime(&clock);
10067
10068     while(*p++ = *template++) if(p[-1] == '%') {
10069         switch(*template++) {
10070           case 0:   *p = 0; return buf;
10071           case 'Y': i = tm->tm_year+1900; break;
10072           case 'y': i = tm->tm_year-100; break;
10073           case 'M': i = tm->tm_mon+1; break;
10074           case 'd': i = tm->tm_mday; break;
10075           case 'h': i = tm->tm_hour; break;
10076           case 'm': i = tm->tm_min; break;
10077           case 's': i = tm->tm_sec; break;
10078           default:  i = 0;
10079         }
10080         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10081     }
10082     return buf;
10083 }
10084
10085 int
10086 CountPlayers (char *p)
10087 {
10088     int n = 0;
10089     while(p = strchr(p, '\n')) p++, n++; // count participants
10090     return n;
10091 }
10092
10093 FILE *
10094 WriteTourneyFile (char *results, FILE *f)
10095 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10096     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10097     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10098         // create a file with tournament description
10099         fprintf(f, "-participants {%s}\n", appData.participants);
10100         fprintf(f, "-seedBase %d\n", appData.seedBase);
10101         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10102         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10103         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10104         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10105         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10106         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10107         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10108         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10109         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10110         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10111         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10112         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10113         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10114         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10115         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10116         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10117         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10118         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10119         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10120         fprintf(f, "-smpCores %d\n", appData.smpCores);
10121         if(searchTime > 0)
10122                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10123         else {
10124                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10125                 fprintf(f, "-tc %s\n", appData.timeControl);
10126                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10127         }
10128         fprintf(f, "-results \"%s\"\n", results);
10129     }
10130     return f;
10131 }
10132
10133 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10134
10135 void
10136 Substitute (char *participants, int expunge)
10137 {
10138     int i, changed, changes=0, nPlayers=0;
10139     char *p, *q, *r, buf[MSG_SIZ];
10140     if(participants == NULL) return;
10141     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10142     r = p = participants; q = appData.participants;
10143     while(*p && *p == *q) {
10144         if(*p == '\n') r = p+1, nPlayers++;
10145         p++; q++;
10146     }
10147     if(*p) { // difference
10148         while(*p && *p++ != '\n');
10149         while(*q && *q++ != '\n');
10150       changed = nPlayers;
10151         changes = 1 + (strcmp(p, q) != 0);
10152     }
10153     if(changes == 1) { // a single engine mnemonic was changed
10154         q = r; while(*q) nPlayers += (*q++ == '\n');
10155         p = buf; while(*r && (*p = *r++) != '\n') p++;
10156         *p = NULLCHAR;
10157         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10158         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10159         if(mnemonic[i]) { // The substitute is valid
10160             FILE *f;
10161             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10162                 flock(fileno(f), LOCK_EX);
10163                 ParseArgsFromFile(f);
10164                 fseek(f, 0, SEEK_SET);
10165                 FREE(appData.participants); appData.participants = participants;
10166                 if(expunge) { // erase results of replaced engine
10167                     int len = strlen(appData.results), w, b, dummy;
10168                     for(i=0; i<len; i++) {
10169                         Pairing(i, nPlayers, &w, &b, &dummy);
10170                         if((w == changed || b == changed) && appData.results[i] == '*') {
10171                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10172                             fclose(f);
10173                             return;
10174                         }
10175                     }
10176                     for(i=0; i<len; i++) {
10177                         Pairing(i, nPlayers, &w, &b, &dummy);
10178                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10179                     }
10180                 }
10181                 WriteTourneyFile(appData.results, f);
10182                 fclose(f); // release lock
10183                 return;
10184             }
10185         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10186     }
10187     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10188     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10189     free(participants);
10190     return;
10191 }
10192
10193 int
10194 CheckPlayers (char *participants)
10195 {
10196         int i;
10197         char buf[MSG_SIZ], *p;
10198         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10199         while(p = strchr(participants, '\n')) {
10200             *p = NULLCHAR;
10201             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10202             if(!mnemonic[i]) {
10203                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10204                 *p = '\n';
10205                 DisplayError(buf, 0);
10206                 return 1;
10207             }
10208             *p = '\n';
10209             participants = p + 1;
10210         }
10211         return 0;
10212 }
10213
10214 int
10215 CreateTourney (char *name)
10216 {
10217         FILE *f;
10218         if(matchMode && strcmp(name, appData.tourneyFile)) {
10219              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10220         }
10221         if(name[0] == NULLCHAR) {
10222             if(appData.participants[0])
10223                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10224             return 0;
10225         }
10226         f = fopen(name, "r");
10227         if(f) { // file exists
10228             ASSIGN(appData.tourneyFile, name);
10229             ParseArgsFromFile(f); // parse it
10230         } else {
10231             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10232             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10233                 DisplayError(_("Not enough participants"), 0);
10234                 return 0;
10235             }
10236             if(CheckPlayers(appData.participants)) return 0;
10237             ASSIGN(appData.tourneyFile, name);
10238             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10239             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10240         }
10241         fclose(f);
10242         appData.noChessProgram = FALSE;
10243         appData.clockMode = TRUE;
10244         SetGNUMode();
10245         return 1;
10246 }
10247
10248 int
10249 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10250 {
10251     char buf[MSG_SIZ], *p, *q;
10252     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10253     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10254     skip = !all && group[0]; // if group requested, we start in skip mode
10255     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10256         p = names; q = buf; header = 0;
10257         while(*p && *p != '\n') *q++ = *p++;
10258         *q = 0;
10259         if(*p == '\n') p++;
10260         if(buf[0] == '#') {
10261             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10262             depth++; // we must be entering a new group
10263             if(all) continue; // suppress printing group headers when complete list requested
10264             header = 1;
10265             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10266         }
10267         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10268         if(engineList[i]) free(engineList[i]);
10269         engineList[i] = strdup(buf);
10270         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10271         if(engineMnemonic[i]) free(engineMnemonic[i]);
10272         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10273             strcat(buf, " (");
10274             sscanf(q + 8, "%s", buf + strlen(buf));
10275             strcat(buf, ")");
10276         }
10277         engineMnemonic[i] = strdup(buf);
10278         i++;
10279     }
10280     engineList[i] = engineMnemonic[i] = NULL;
10281     return i;
10282 }
10283
10284 // following implemented as macro to avoid type limitations
10285 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10286
10287 void
10288 SwapEngines (int n)
10289 {   // swap settings for first engine and other engine (so far only some selected options)
10290     int h;
10291     char *p;
10292     if(n == 0) return;
10293     SWAP(directory, p)
10294     SWAP(chessProgram, p)
10295     SWAP(isUCI, h)
10296     SWAP(hasOwnBookUCI, h)
10297     SWAP(protocolVersion, h)
10298     SWAP(reuse, h)
10299     SWAP(scoreIsAbsolute, h)
10300     SWAP(timeOdds, h)
10301     SWAP(logo, p)
10302     SWAP(pgnName, p)
10303     SWAP(pvSAN, h)
10304     SWAP(engOptions, p)
10305     SWAP(engInitString, p)
10306     SWAP(computerString, p)
10307     SWAP(features, p)
10308     SWAP(fenOverride, p)
10309     SWAP(NPS, h)
10310     SWAP(accumulateTC, h)
10311     SWAP(host, p)
10312 }
10313
10314 int
10315 GetEngineLine (char *s, int n)
10316 {
10317     int i;
10318     char buf[MSG_SIZ];
10319     extern char *icsNames;
10320     if(!s || !*s) return 0;
10321     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10322     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10323     if(!mnemonic[i]) return 0;
10324     if(n == 11) return 1; // just testing if there was a match
10325     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10326     if(n == 1) SwapEngines(n);
10327     ParseArgsFromString(buf);
10328     if(n == 1) SwapEngines(n);
10329     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10330         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10331         ParseArgsFromString(buf);
10332     }
10333     return 1;
10334 }
10335
10336 int
10337 SetPlayer (int player, char *p)
10338 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10339     int i;
10340     char buf[MSG_SIZ], *engineName;
10341     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10342     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10343     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10344     if(mnemonic[i]) {
10345         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10346         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10347         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10348         ParseArgsFromString(buf);
10349     }
10350     free(engineName);
10351     return i;
10352 }
10353
10354 char *recentEngines;
10355
10356 void
10357 RecentEngineEvent (int nr)
10358 {
10359     int n;
10360 //    SwapEngines(1); // bump first to second
10361 //    ReplaceEngine(&second, 1); // and load it there
10362     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10363     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10364     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10365         ReplaceEngine(&first, 0);
10366         FloatToFront(&appData.recentEngineList, command[n]);
10367     }
10368 }
10369
10370 int
10371 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10372 {   // determine players from game number
10373     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10374
10375     if(appData.tourneyType == 0) {
10376         roundsPerCycle = (nPlayers - 1) | 1;
10377         pairingsPerRound = nPlayers / 2;
10378     } else if(appData.tourneyType > 0) {
10379         roundsPerCycle = nPlayers - appData.tourneyType;
10380         pairingsPerRound = appData.tourneyType;
10381     }
10382     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10383     gamesPerCycle = gamesPerRound * roundsPerCycle;
10384     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10385     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10386     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10387     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10388     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10389     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10390
10391     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10392     if(appData.roundSync) *syncInterval = gamesPerRound;
10393
10394     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10395
10396     if(appData.tourneyType == 0) {
10397         if(curPairing == (nPlayers-1)/2 ) {
10398             *whitePlayer = curRound;
10399             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10400         } else {
10401             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10402             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10403             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10404             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10405         }
10406     } else if(appData.tourneyType > 1) {
10407         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10408         *whitePlayer = curRound + appData.tourneyType;
10409     } else if(appData.tourneyType > 0) {
10410         *whitePlayer = curPairing;
10411         *blackPlayer = curRound + appData.tourneyType;
10412     }
10413
10414     // take care of white/black alternation per round.
10415     // For cycles and games this is already taken care of by default, derived from matchGame!
10416     return curRound & 1;
10417 }
10418
10419 int
10420 NextTourneyGame (int nr, int *swapColors)
10421 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10422     char *p, *q;
10423     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10424     FILE *tf;
10425     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10426     tf = fopen(appData.tourneyFile, "r");
10427     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10428     ParseArgsFromFile(tf); fclose(tf);
10429     InitTimeControls(); // TC might be altered from tourney file
10430
10431     nPlayers = CountPlayers(appData.participants); // count participants
10432     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10433     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10434
10435     if(syncInterval) {
10436         p = q = appData.results;
10437         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10438         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10439             DisplayMessage(_("Waiting for other game(s)"),"");
10440             waitingForGame = TRUE;
10441             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10442             return 0;
10443         }
10444         waitingForGame = FALSE;
10445     }
10446
10447     if(appData.tourneyType < 0) {
10448         if(nr>=0 && !pairingReceived) {
10449             char buf[1<<16];
10450             if(pairing.pr == NoProc) {
10451                 if(!appData.pairingEngine[0]) {
10452                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10453                     return 0;
10454                 }
10455                 StartChessProgram(&pairing); // starts the pairing engine
10456             }
10457             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10458             SendToProgram(buf, &pairing);
10459             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10460             SendToProgram(buf, &pairing);
10461             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10462         }
10463         pairingReceived = 0;                              // ... so we continue here
10464         *swapColors = 0;
10465         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10466         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10467         matchGame = 1; roundNr = nr / syncInterval + 1;
10468     }
10469
10470     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10471
10472     // redefine engines, engine dir, etc.
10473     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10474     if(first.pr == NoProc) {
10475       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10476       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10477     }
10478     if(second.pr == NoProc) {
10479       SwapEngines(1);
10480       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10481       SwapEngines(1);         // and make that valid for second engine by swapping
10482       InitEngine(&second, 1);
10483     }
10484     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10485     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10486     return 1;
10487 }
10488
10489 void
10490 NextMatchGame ()
10491 {   // performs game initialization that does not invoke engines, and then tries to start the game
10492     int res, firstWhite, swapColors = 0;
10493     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10494     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
10495         char buf[MSG_SIZ];
10496         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10497         if(strcmp(buf, currentDebugFile)) { // name has changed
10498             FILE *f = fopen(buf, "w");
10499             if(f) { // if opening the new file failed, just keep using the old one
10500                 ASSIGN(currentDebugFile, buf);
10501                 fclose(debugFP);
10502                 debugFP = f;
10503             }
10504             if(appData.serverFileName) {
10505                 if(serverFP) fclose(serverFP);
10506                 serverFP = fopen(appData.serverFileName, "w");
10507                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10508                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10509             }
10510         }
10511     }
10512     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10513     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10514     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10515     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10516     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10517     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10518     Reset(FALSE, first.pr != NoProc);
10519     res = LoadGameOrPosition(matchGame); // setup game
10520     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10521     if(!res) return; // abort when bad game/pos file
10522     TwoMachinesEvent();
10523 }
10524
10525 void
10526 UserAdjudicationEvent (int result)
10527 {
10528     ChessMove gameResult = GameIsDrawn;
10529
10530     if( result > 0 ) {
10531         gameResult = WhiteWins;
10532     }
10533     else if( result < 0 ) {
10534         gameResult = BlackWins;
10535     }
10536
10537     if( gameMode == TwoMachinesPlay ) {
10538         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10539     }
10540 }
10541
10542
10543 // [HGM] save: calculate checksum of game to make games easily identifiable
10544 int
10545 StringCheckSum (char *s)
10546 {
10547         int i = 0;
10548         if(s==NULL) return 0;
10549         while(*s) i = i*259 + *s++;
10550         return i;
10551 }
10552
10553 int
10554 GameCheckSum ()
10555 {
10556         int i, sum=0;
10557         for(i=backwardMostMove; i<forwardMostMove; i++) {
10558                 sum += pvInfoList[i].depth;
10559                 sum += StringCheckSum(parseList[i]);
10560                 sum += StringCheckSum(commentList[i]);
10561                 sum *= 261;
10562         }
10563         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10564         return sum + StringCheckSum(commentList[i]);
10565 } // end of save patch
10566
10567 void
10568 GameEnds (ChessMove result, char *resultDetails, int whosays)
10569 {
10570     GameMode nextGameMode;
10571     int isIcsGame;
10572     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10573
10574     if(endingGame) return; /* [HGM] crash: forbid recursion */
10575     endingGame = 1;
10576     if(twoBoards) { // [HGM] dual: switch back to one board
10577         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10578         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10579     }
10580     if (appData.debugMode) {
10581       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10582               result, resultDetails ? resultDetails : "(null)", whosays);
10583     }
10584
10585     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10586
10587     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10588
10589     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10590         /* If we are playing on ICS, the server decides when the
10591            game is over, but the engine can offer to draw, claim
10592            a draw, or resign.
10593          */
10594 #if ZIPPY
10595         if (appData.zippyPlay && first.initDone) {
10596             if (result == GameIsDrawn) {
10597                 /* In case draw still needs to be claimed */
10598                 SendToICS(ics_prefix);
10599                 SendToICS("draw\n");
10600             } else if (StrCaseStr(resultDetails, "resign")) {
10601                 SendToICS(ics_prefix);
10602                 SendToICS("resign\n");
10603             }
10604         }
10605 #endif
10606         endingGame = 0; /* [HGM] crash */
10607         return;
10608     }
10609
10610     /* If we're loading the game from a file, stop */
10611     if (whosays == GE_FILE) {
10612       (void) StopLoadGameTimer();
10613       gameFileFP = NULL;
10614     }
10615
10616     /* Cancel draw offers */
10617     first.offeredDraw = second.offeredDraw = 0;
10618
10619     /* If this is an ICS game, only ICS can really say it's done;
10620        if not, anyone can. */
10621     isIcsGame = (gameMode == IcsPlayingWhite ||
10622                  gameMode == IcsPlayingBlack ||
10623                  gameMode == IcsObserving    ||
10624                  gameMode == IcsExamining);
10625
10626     if (!isIcsGame || whosays == GE_ICS) {
10627         /* OK -- not an ICS game, or ICS said it was done */
10628         StopClocks();
10629         if (!isIcsGame && !appData.noChessProgram)
10630           SetUserThinkingEnables();
10631
10632         /* [HGM] if a machine claims the game end we verify this claim */
10633         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10634             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10635                 char claimer;
10636                 ChessMove trueResult = (ChessMove) -1;
10637
10638                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10639                                             first.twoMachinesColor[0] :
10640                                             second.twoMachinesColor[0] ;
10641
10642                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10643                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10644                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10645                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10646                 } else
10647                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10648                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10649                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10650                 } else
10651                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10652                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10653                 }
10654
10655                 // now verify win claims, but not in drop games, as we don't understand those yet
10656                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10657                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10658                     (result == WhiteWins && claimer == 'w' ||
10659                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10660                       if (appData.debugMode) {
10661                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10662                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10663                       }
10664                       if(result != trueResult) {
10665                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10666                               result = claimer == 'w' ? BlackWins : WhiteWins;
10667                               resultDetails = buf;
10668                       }
10669                 } else
10670                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10671                     && (forwardMostMove <= backwardMostMove ||
10672                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10673                         (claimer=='b')==(forwardMostMove&1))
10674                                                                                   ) {
10675                       /* [HGM] verify: draws that were not flagged are false claims */
10676                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10677                       result = claimer == 'w' ? BlackWins : WhiteWins;
10678                       resultDetails = buf;
10679                 }
10680                 /* (Claiming a loss is accepted no questions asked!) */
10681             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10682                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10683                 result = GameUnfinished;
10684                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10685             }
10686             /* [HGM] bare: don't allow bare King to win */
10687             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10688                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10689                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10690                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10691                && result != GameIsDrawn)
10692             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10693                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10694                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10695                         if(p >= 0 && p <= (int)WhiteKing) k++;
10696                 }
10697                 if (appData.debugMode) {
10698                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10699                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10700                 }
10701                 if(k <= 1) {
10702                         result = GameIsDrawn;
10703                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10704                         resultDetails = buf;
10705                 }
10706             }
10707         }
10708
10709
10710         if(serverMoves != NULL && !loadFlag) { char c = '=';
10711             if(result==WhiteWins) c = '+';
10712             if(result==BlackWins) c = '-';
10713             if(resultDetails != NULL)
10714                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10715         }
10716         if (resultDetails != NULL) {
10717             gameInfo.result = result;
10718             gameInfo.resultDetails = StrSave(resultDetails);
10719
10720             /* display last move only if game was not loaded from file */
10721             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10722                 DisplayMove(currentMove - 1);
10723
10724             if (forwardMostMove != 0) {
10725                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10726                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10727                                                                 ) {
10728                     if (*appData.saveGameFile != NULLCHAR) {
10729                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10730                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10731                         else
10732                         SaveGameToFile(appData.saveGameFile, TRUE);
10733                     } else if (appData.autoSaveGames) {
10734                         AutoSaveGame();
10735                     }
10736                     if (*appData.savePositionFile != NULLCHAR) {
10737                         SavePositionToFile(appData.savePositionFile);
10738                     }
10739                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10740                 }
10741             }
10742
10743             /* Tell program how game ended in case it is learning */
10744             /* [HGM] Moved this to after saving the PGN, just in case */
10745             /* engine died and we got here through time loss. In that */
10746             /* case we will get a fatal error writing the pipe, which */
10747             /* would otherwise lose us the PGN.                       */
10748             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10749             /* output during GameEnds should never be fatal anymore   */
10750             if (gameMode == MachinePlaysWhite ||
10751                 gameMode == MachinePlaysBlack ||
10752                 gameMode == TwoMachinesPlay ||
10753                 gameMode == IcsPlayingWhite ||
10754                 gameMode == IcsPlayingBlack ||
10755                 gameMode == BeginningOfGame) {
10756                 char buf[MSG_SIZ];
10757                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10758                         resultDetails);
10759                 if (first.pr != NoProc) {
10760                     SendToProgram(buf, &first);
10761                 }
10762                 if (second.pr != NoProc &&
10763                     gameMode == TwoMachinesPlay) {
10764                     SendToProgram(buf, &second);
10765                 }
10766             }
10767         }
10768
10769         if (appData.icsActive) {
10770             if (appData.quietPlay &&
10771                 (gameMode == IcsPlayingWhite ||
10772                  gameMode == IcsPlayingBlack)) {
10773                 SendToICS(ics_prefix);
10774                 SendToICS("set shout 1\n");
10775             }
10776             nextGameMode = IcsIdle;
10777             ics_user_moved = FALSE;
10778             /* clean up premove.  It's ugly when the game has ended and the
10779              * premove highlights are still on the board.
10780              */
10781             if (gotPremove) {
10782               gotPremove = FALSE;
10783               ClearPremoveHighlights();
10784               DrawPosition(FALSE, boards[currentMove]);
10785             }
10786             if (whosays == GE_ICS) {
10787                 switch (result) {
10788                 case WhiteWins:
10789                     if (gameMode == IcsPlayingWhite)
10790                         PlayIcsWinSound();
10791                     else if(gameMode == IcsPlayingBlack)
10792                         PlayIcsLossSound();
10793                     break;
10794                 case BlackWins:
10795                     if (gameMode == IcsPlayingBlack)
10796                         PlayIcsWinSound();
10797                     else if(gameMode == IcsPlayingWhite)
10798                         PlayIcsLossSound();
10799                     break;
10800                 case GameIsDrawn:
10801                     PlayIcsDrawSound();
10802                     break;
10803                 default:
10804                     PlayIcsUnfinishedSound();
10805                 }
10806             }
10807         } else if (gameMode == EditGame ||
10808                    gameMode == PlayFromGameFile ||
10809                    gameMode == AnalyzeMode ||
10810                    gameMode == AnalyzeFile) {
10811             nextGameMode = gameMode;
10812         } else {
10813             nextGameMode = EndOfGame;
10814         }
10815         pausing = FALSE;
10816         ModeHighlight();
10817     } else {
10818         nextGameMode = gameMode;
10819     }
10820
10821     if (appData.noChessProgram) {
10822         gameMode = nextGameMode;
10823         ModeHighlight();
10824         endingGame = 0; /* [HGM] crash */
10825         return;
10826     }
10827
10828     if (first.reuse) {
10829         /* Put first chess program into idle state */
10830         if (first.pr != NoProc &&
10831             (gameMode == MachinePlaysWhite ||
10832              gameMode == MachinePlaysBlack ||
10833              gameMode == TwoMachinesPlay ||
10834              gameMode == IcsPlayingWhite ||
10835              gameMode == IcsPlayingBlack ||
10836              gameMode == BeginningOfGame)) {
10837             SendToProgram("force\n", &first);
10838             if (first.usePing) {
10839               char buf[MSG_SIZ];
10840               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10841               SendToProgram(buf, &first);
10842             }
10843         }
10844     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10845         /* Kill off first chess program */
10846         if (first.isr != NULL)
10847           RemoveInputSource(first.isr);
10848         first.isr = NULL;
10849
10850         if (first.pr != NoProc) {
10851             ExitAnalyzeMode();
10852             DoSleep( appData.delayBeforeQuit );
10853             SendToProgram("quit\n", &first);
10854             DoSleep( appData.delayAfterQuit );
10855             DestroyChildProcess(first.pr, first.useSigterm);
10856             first.reload = TRUE;
10857         }
10858         first.pr = NoProc;
10859     }
10860     if (second.reuse) {
10861         /* Put second chess program into idle state */
10862         if (second.pr != NoProc &&
10863             gameMode == TwoMachinesPlay) {
10864             SendToProgram("force\n", &second);
10865             if (second.usePing) {
10866               char buf[MSG_SIZ];
10867               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10868               SendToProgram(buf, &second);
10869             }
10870         }
10871     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10872         /* Kill off second chess program */
10873         if (second.isr != NULL)
10874           RemoveInputSource(second.isr);
10875         second.isr = NULL;
10876
10877         if (second.pr != NoProc) {
10878             DoSleep( appData.delayBeforeQuit );
10879             SendToProgram("quit\n", &second);
10880             DoSleep( appData.delayAfterQuit );
10881             DestroyChildProcess(second.pr, second.useSigterm);
10882             second.reload = TRUE;
10883         }
10884         second.pr = NoProc;
10885     }
10886
10887     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10888         char resChar = '=';
10889         switch (result) {
10890         case WhiteWins:
10891           resChar = '+';
10892           if (first.twoMachinesColor[0] == 'w') {
10893             first.matchWins++;
10894           } else {
10895             second.matchWins++;
10896           }
10897           break;
10898         case BlackWins:
10899           resChar = '-';
10900           if (first.twoMachinesColor[0] == 'b') {
10901             first.matchWins++;
10902           } else {
10903             second.matchWins++;
10904           }
10905           break;
10906         case GameUnfinished:
10907           resChar = ' ';
10908         default:
10909           break;
10910         }
10911
10912         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10913         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10914             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10915             ReserveGame(nextGame, resChar); // sets nextGame
10916             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10917             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10918         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10919
10920         if (nextGame <= appData.matchGames && !abortMatch) {
10921             gameMode = nextGameMode;
10922             matchGame = nextGame; // this will be overruled in tourney mode!
10923             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10924             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10925             endingGame = 0; /* [HGM] crash */
10926             return;
10927         } else {
10928             gameMode = nextGameMode;
10929             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10930                      first.tidy, second.tidy,
10931                      first.matchWins, second.matchWins,
10932                      appData.matchGames - (first.matchWins + second.matchWins));
10933             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10934             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10935             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10936             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10937                 first.twoMachinesColor = "black\n";
10938                 second.twoMachinesColor = "white\n";
10939             } else {
10940                 first.twoMachinesColor = "white\n";
10941                 second.twoMachinesColor = "black\n";
10942             }
10943         }
10944     }
10945     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10946         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10947       ExitAnalyzeMode();
10948     gameMode = nextGameMode;
10949     ModeHighlight();
10950     endingGame = 0;  /* [HGM] crash */
10951     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10952         if(matchMode == TRUE) { // match through command line: exit with or without popup
10953             if(ranking) {
10954                 ToNrEvent(forwardMostMove);
10955                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10956                 else ExitEvent(0);
10957             } else DisplayFatalError(buf, 0, 0);
10958         } else { // match through menu; just stop, with or without popup
10959             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10960             ModeHighlight();
10961             if(ranking){
10962                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10963             } else DisplayNote(buf);
10964       }
10965       if(ranking) free(ranking);
10966     }
10967 }
10968
10969 /* Assumes program was just initialized (initString sent).
10970    Leaves program in force mode. */
10971 void
10972 FeedMovesToProgram (ChessProgramState *cps, int upto)
10973 {
10974     int i;
10975
10976     if (appData.debugMode)
10977       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10978               startedFromSetupPosition ? "position and " : "",
10979               backwardMostMove, upto, cps->which);
10980     if(currentlyInitializedVariant != gameInfo.variant) {
10981       char buf[MSG_SIZ];
10982         // [HGM] variantswitch: make engine aware of new variant
10983         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10984                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10985         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10986         SendToProgram(buf, cps);
10987         currentlyInitializedVariant = gameInfo.variant;
10988     }
10989     SendToProgram("force\n", cps);
10990     if (startedFromSetupPosition) {
10991         SendBoard(cps, backwardMostMove);
10992     if (appData.debugMode) {
10993         fprintf(debugFP, "feedMoves\n");
10994     }
10995     }
10996     for (i = backwardMostMove; i < upto; i++) {
10997         SendMoveToProgram(i, cps);
10998     }
10999 }
11000
11001
11002 int
11003 ResurrectChessProgram ()
11004 {
11005      /* The chess program may have exited.
11006         If so, restart it and feed it all the moves made so far. */
11007     static int doInit = 0;
11008
11009     if (appData.noChessProgram) return 1;
11010
11011     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11012         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
11013         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11014         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11015     } else {
11016         if (first.pr != NoProc) return 1;
11017         StartChessProgram(&first);
11018     }
11019     InitChessProgram(&first, FALSE);
11020     FeedMovesToProgram(&first, currentMove);
11021
11022     if (!first.sendTime) {
11023         /* can't tell gnuchess what its clock should read,
11024            so we bow to its notion. */
11025         ResetClocks();
11026         timeRemaining[0][currentMove] = whiteTimeRemaining;
11027         timeRemaining[1][currentMove] = blackTimeRemaining;
11028     }
11029
11030     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11031                 appData.icsEngineAnalyze) && first.analysisSupport) {
11032       SendToProgram("analyze\n", &first);
11033       first.analyzing = TRUE;
11034     }
11035     return 1;
11036 }
11037
11038 /*
11039  * Button procedures
11040  */
11041 void
11042 Reset (int redraw, int init)
11043 {
11044     int i;
11045
11046     if (appData.debugMode) {
11047         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11048                 redraw, init, gameMode);
11049     }
11050     CleanupTail(); // [HGM] vari: delete any stored variations
11051     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11052     pausing = pauseExamInvalid = FALSE;
11053     startedFromSetupPosition = blackPlaysFirst = FALSE;
11054     firstMove = TRUE;
11055     whiteFlag = blackFlag = FALSE;
11056     userOfferedDraw = FALSE;
11057     hintRequested = bookRequested = FALSE;
11058     first.maybeThinking = FALSE;
11059     second.maybeThinking = FALSE;
11060     first.bookSuspend = FALSE; // [HGM] book
11061     second.bookSuspend = FALSE;
11062     thinkOutput[0] = NULLCHAR;
11063     lastHint[0] = NULLCHAR;
11064     ClearGameInfo(&gameInfo);
11065     gameInfo.variant = StringToVariant(appData.variant);
11066     ics_user_moved = ics_clock_paused = FALSE;
11067     ics_getting_history = H_FALSE;
11068     ics_gamenum = -1;
11069     white_holding[0] = black_holding[0] = NULLCHAR;
11070     ClearProgramStats();
11071     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11072
11073     ResetFrontEnd();
11074     ClearHighlights();
11075     flipView = appData.flipView;
11076     ClearPremoveHighlights();
11077     gotPremove = FALSE;
11078     alarmSounded = FALSE;
11079
11080     GameEnds(EndOfFile, NULL, GE_PLAYER);
11081     if(appData.serverMovesName != NULL) {
11082         /* [HGM] prepare to make moves file for broadcasting */
11083         clock_t t = clock();
11084         if(serverMoves != NULL) fclose(serverMoves);
11085         serverMoves = fopen(appData.serverMovesName, "r");
11086         if(serverMoves != NULL) {
11087             fclose(serverMoves);
11088             /* delay 15 sec before overwriting, so all clients can see end */
11089             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11090         }
11091         serverMoves = fopen(appData.serverMovesName, "w");
11092     }
11093
11094     ExitAnalyzeMode();
11095     gameMode = BeginningOfGame;
11096     ModeHighlight();
11097     if(appData.icsActive) gameInfo.variant = VariantNormal;
11098     currentMove = forwardMostMove = backwardMostMove = 0;
11099     MarkTargetSquares(1);
11100     InitPosition(redraw);
11101     for (i = 0; i < MAX_MOVES; i++) {
11102         if (commentList[i] != NULL) {
11103             free(commentList[i]);
11104             commentList[i] = NULL;
11105         }
11106     }
11107     ResetClocks();
11108     timeRemaining[0][0] = whiteTimeRemaining;
11109     timeRemaining[1][0] = blackTimeRemaining;
11110
11111     if (first.pr == NoProc) {
11112         StartChessProgram(&first);
11113     }
11114     if (init) {
11115             InitChessProgram(&first, startedFromSetupPosition);
11116     }
11117     DisplayTitle("");
11118     DisplayMessage("", "");
11119     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11120     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11121     ClearMap();        // [HGM] exclude: invalidate map
11122 }
11123
11124 void
11125 AutoPlayGameLoop ()
11126 {
11127     for (;;) {
11128         if (!AutoPlayOneMove())
11129           return;
11130         if (matchMode || appData.timeDelay == 0)
11131           continue;
11132         if (appData.timeDelay < 0)
11133           return;
11134         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11135         break;
11136     }
11137 }
11138
11139 void
11140 AnalyzeNextGame()
11141 {
11142     ReloadGame(1); // next game
11143 }
11144
11145 int
11146 AutoPlayOneMove ()
11147 {
11148     int fromX, fromY, toX, toY;
11149
11150     if (appData.debugMode) {
11151       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11152     }
11153
11154     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11155       return FALSE;
11156
11157     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11158       pvInfoList[currentMove].depth = programStats.depth;
11159       pvInfoList[currentMove].score = programStats.score;
11160       pvInfoList[currentMove].time  = 0;
11161       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11162     }
11163
11164     if (currentMove >= forwardMostMove) {
11165       if(gameMode == AnalyzeFile) {
11166           if(appData.loadGameIndex == -1) {
11167             GameEnds(EndOfFile, NULL, GE_FILE);
11168           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11169           } else {
11170           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11171         }
11172       }
11173 //      gameMode = EndOfGame;
11174 //      ModeHighlight();
11175
11176       /* [AS] Clear current move marker at the end of a game */
11177       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11178
11179       return FALSE;
11180     }
11181
11182     toX = moveList[currentMove][2] - AAA;
11183     toY = moveList[currentMove][3] - ONE;
11184
11185     if (moveList[currentMove][1] == '@') {
11186         if (appData.highlightLastMove) {
11187             SetHighlights(-1, -1, toX, toY);
11188         }
11189     } else {
11190         fromX = moveList[currentMove][0] - AAA;
11191         fromY = moveList[currentMove][1] - ONE;
11192
11193         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11194
11195         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11196
11197         if (appData.highlightLastMove) {
11198             SetHighlights(fromX, fromY, toX, toY);
11199         }
11200     }
11201     DisplayMove(currentMove);
11202     SendMoveToProgram(currentMove++, &first);
11203     DisplayBothClocks();
11204     DrawPosition(FALSE, boards[currentMove]);
11205     // [HGM] PV info: always display, routine tests if empty
11206     DisplayComment(currentMove - 1, commentList[currentMove]);
11207     return TRUE;
11208 }
11209
11210
11211 int
11212 LoadGameOneMove (ChessMove readAhead)
11213 {
11214     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11215     char promoChar = NULLCHAR;
11216     ChessMove moveType;
11217     char move[MSG_SIZ];
11218     char *p, *q;
11219
11220     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11221         gameMode != AnalyzeMode && gameMode != Training) {
11222         gameFileFP = NULL;
11223         return FALSE;
11224     }
11225
11226     yyboardindex = forwardMostMove;
11227     if (readAhead != EndOfFile) {
11228       moveType = readAhead;
11229     } else {
11230       if (gameFileFP == NULL)
11231           return FALSE;
11232       moveType = (ChessMove) Myylex();
11233     }
11234
11235     done = FALSE;
11236     switch (moveType) {
11237       case Comment:
11238         if (appData.debugMode)
11239           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11240         p = yy_text;
11241
11242         /* append the comment but don't display it */
11243         AppendComment(currentMove, p, FALSE);
11244         return TRUE;
11245
11246       case WhiteCapturesEnPassant:
11247       case BlackCapturesEnPassant:
11248       case WhitePromotion:
11249       case BlackPromotion:
11250       case WhiteNonPromotion:
11251       case BlackNonPromotion:
11252       case NormalMove:
11253       case WhiteKingSideCastle:
11254       case WhiteQueenSideCastle:
11255       case BlackKingSideCastle:
11256       case BlackQueenSideCastle:
11257       case WhiteKingSideCastleWild:
11258       case WhiteQueenSideCastleWild:
11259       case BlackKingSideCastleWild:
11260       case BlackQueenSideCastleWild:
11261       /* PUSH Fabien */
11262       case WhiteHSideCastleFR:
11263       case WhiteASideCastleFR:
11264       case BlackHSideCastleFR:
11265       case BlackASideCastleFR:
11266       /* POP Fabien */
11267         if (appData.debugMode)
11268           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11269         fromX = currentMoveString[0] - AAA;
11270         fromY = currentMoveString[1] - ONE;
11271         toX = currentMoveString[2] - AAA;
11272         toY = currentMoveString[3] - ONE;
11273         promoChar = currentMoveString[4];
11274         break;
11275
11276       case WhiteDrop:
11277       case BlackDrop:
11278         if (appData.debugMode)
11279           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11280         fromX = moveType == WhiteDrop ?
11281           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11282         (int) CharToPiece(ToLower(currentMoveString[0]));
11283         fromY = DROP_RANK;
11284         toX = currentMoveString[2] - AAA;
11285         toY = currentMoveString[3] - ONE;
11286         break;
11287
11288       case WhiteWins:
11289       case BlackWins:
11290       case GameIsDrawn:
11291       case GameUnfinished:
11292         if (appData.debugMode)
11293           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11294         p = strchr(yy_text, '{');
11295         if (p == NULL) p = strchr(yy_text, '(');
11296         if (p == NULL) {
11297             p = yy_text;
11298             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11299         } else {
11300             q = strchr(p, *p == '{' ? '}' : ')');
11301             if (q != NULL) *q = NULLCHAR;
11302             p++;
11303         }
11304         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11305         GameEnds(moveType, p, GE_FILE);
11306         done = TRUE;
11307         if (cmailMsgLoaded) {
11308             ClearHighlights();
11309             flipView = WhiteOnMove(currentMove);
11310             if (moveType == GameUnfinished) flipView = !flipView;
11311             if (appData.debugMode)
11312               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11313         }
11314         break;
11315
11316       case EndOfFile:
11317         if (appData.debugMode)
11318           fprintf(debugFP, "Parser hit end of file\n");
11319         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11320           case MT_NONE:
11321           case MT_CHECK:
11322             break;
11323           case MT_CHECKMATE:
11324           case MT_STAINMATE:
11325             if (WhiteOnMove(currentMove)) {
11326                 GameEnds(BlackWins, "Black mates", GE_FILE);
11327             } else {
11328                 GameEnds(WhiteWins, "White mates", GE_FILE);
11329             }
11330             break;
11331           case MT_STALEMATE:
11332             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11333             break;
11334         }
11335         done = TRUE;
11336         break;
11337
11338       case MoveNumberOne:
11339         if (lastLoadGameStart == GNUChessGame) {
11340             /* GNUChessGames have numbers, but they aren't move numbers */
11341             if (appData.debugMode)
11342               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11343                       yy_text, (int) moveType);
11344             return LoadGameOneMove(EndOfFile); /* tail recursion */
11345         }
11346         /* else fall thru */
11347
11348       case XBoardGame:
11349       case GNUChessGame:
11350       case PGNTag:
11351         /* Reached start of next game in file */
11352         if (appData.debugMode)
11353           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11354         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11355           case MT_NONE:
11356           case MT_CHECK:
11357             break;
11358           case MT_CHECKMATE:
11359           case MT_STAINMATE:
11360             if (WhiteOnMove(currentMove)) {
11361                 GameEnds(BlackWins, "Black mates", GE_FILE);
11362             } else {
11363                 GameEnds(WhiteWins, "White mates", GE_FILE);
11364             }
11365             break;
11366           case MT_STALEMATE:
11367             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11368             break;
11369         }
11370         done = TRUE;
11371         break;
11372
11373       case PositionDiagram:     /* should not happen; ignore */
11374       case ElapsedTime:         /* ignore */
11375       case NAG:                 /* ignore */
11376         if (appData.debugMode)
11377           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11378                   yy_text, (int) moveType);
11379         return LoadGameOneMove(EndOfFile); /* tail recursion */
11380
11381       case IllegalMove:
11382         if (appData.testLegality) {
11383             if (appData.debugMode)
11384               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11385             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11386                     (forwardMostMove / 2) + 1,
11387                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11388             DisplayError(move, 0);
11389             done = TRUE;
11390         } else {
11391             if (appData.debugMode)
11392               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11393                       yy_text, currentMoveString);
11394             fromX = currentMoveString[0] - AAA;
11395             fromY = currentMoveString[1] - ONE;
11396             toX = currentMoveString[2] - AAA;
11397             toY = currentMoveString[3] - ONE;
11398             promoChar = currentMoveString[4];
11399         }
11400         break;
11401
11402       case AmbiguousMove:
11403         if (appData.debugMode)
11404           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11405         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11406                 (forwardMostMove / 2) + 1,
11407                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11408         DisplayError(move, 0);
11409         done = TRUE;
11410         break;
11411
11412       default:
11413       case ImpossibleMove:
11414         if (appData.debugMode)
11415           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11416         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11417                 (forwardMostMove / 2) + 1,
11418                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11419         DisplayError(move, 0);
11420         done = TRUE;
11421         break;
11422     }
11423
11424     if (done) {
11425         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11426             DrawPosition(FALSE, boards[currentMove]);
11427             DisplayBothClocks();
11428             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11429               DisplayComment(currentMove - 1, commentList[currentMove]);
11430         }
11431         (void) StopLoadGameTimer();
11432         gameFileFP = NULL;
11433         cmailOldMove = forwardMostMove;
11434         return FALSE;
11435     } else {
11436         /* currentMoveString is set as a side-effect of yylex */
11437
11438         thinkOutput[0] = NULLCHAR;
11439         MakeMove(fromX, fromY, toX, toY, promoChar);
11440         currentMove = forwardMostMove;
11441         return TRUE;
11442     }
11443 }
11444
11445 /* Load the nth game from the given file */
11446 int
11447 LoadGameFromFile (char *filename, int n, char *title, int useList)
11448 {
11449     FILE *f;
11450     char buf[MSG_SIZ];
11451
11452     if (strcmp(filename, "-") == 0) {
11453         f = stdin;
11454         title = "stdin";
11455     } else {
11456         f = fopen(filename, "rb");
11457         if (f == NULL) {
11458           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11459             DisplayError(buf, errno);
11460             return FALSE;
11461         }
11462     }
11463     if (fseek(f, 0, 0) == -1) {
11464         /* f is not seekable; probably a pipe */
11465         useList = FALSE;
11466     }
11467     if (useList && n == 0) {
11468         int error = GameListBuild(f);
11469         if (error) {
11470             DisplayError(_("Cannot build game list"), error);
11471         } else if (!ListEmpty(&gameList) &&
11472                    ((ListGame *) gameList.tailPred)->number > 1) {
11473             GameListPopUp(f, title);
11474             return TRUE;
11475         }
11476         GameListDestroy();
11477         n = 1;
11478     }
11479     if (n == 0) n = 1;
11480     return LoadGame(f, n, title, FALSE);
11481 }
11482
11483
11484 void
11485 MakeRegisteredMove ()
11486 {
11487     int fromX, fromY, toX, toY;
11488     char promoChar;
11489     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11490         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11491           case CMAIL_MOVE:
11492           case CMAIL_DRAW:
11493             if (appData.debugMode)
11494               fprintf(debugFP, "Restoring %s for game %d\n",
11495                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11496
11497             thinkOutput[0] = NULLCHAR;
11498             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11499             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11500             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11501             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11502             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11503             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11504             MakeMove(fromX, fromY, toX, toY, promoChar);
11505             ShowMove(fromX, fromY, toX, toY);
11506
11507             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11508               case MT_NONE:
11509               case MT_CHECK:
11510                 break;
11511
11512               case MT_CHECKMATE:
11513               case MT_STAINMATE:
11514                 if (WhiteOnMove(currentMove)) {
11515                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11516                 } else {
11517                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11518                 }
11519                 break;
11520
11521               case MT_STALEMATE:
11522                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11523                 break;
11524             }
11525
11526             break;
11527
11528           case CMAIL_RESIGN:
11529             if (WhiteOnMove(currentMove)) {
11530                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11531             } else {
11532                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11533             }
11534             break;
11535
11536           case CMAIL_ACCEPT:
11537             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11538             break;
11539
11540           default:
11541             break;
11542         }
11543     }
11544
11545     return;
11546 }
11547
11548 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11549 int
11550 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11551 {
11552     int retVal;
11553
11554     if (gameNumber > nCmailGames) {
11555         DisplayError(_("No more games in this message"), 0);
11556         return FALSE;
11557     }
11558     if (f == lastLoadGameFP) {
11559         int offset = gameNumber - lastLoadGameNumber;
11560         if (offset == 0) {
11561             cmailMsg[0] = NULLCHAR;
11562             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11563                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11564                 nCmailMovesRegistered--;
11565             }
11566             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11567             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11568                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11569             }
11570         } else {
11571             if (! RegisterMove()) return FALSE;
11572         }
11573     }
11574
11575     retVal = LoadGame(f, gameNumber, title, useList);
11576
11577     /* Make move registered during previous look at this game, if any */
11578     MakeRegisteredMove();
11579
11580     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11581         commentList[currentMove]
11582           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11583         DisplayComment(currentMove - 1, commentList[currentMove]);
11584     }
11585
11586     return retVal;
11587 }
11588
11589 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11590 int
11591 ReloadGame (int offset)
11592 {
11593     int gameNumber = lastLoadGameNumber + offset;
11594     if (lastLoadGameFP == NULL) {
11595         DisplayError(_("No game has been loaded yet"), 0);
11596         return FALSE;
11597     }
11598     if (gameNumber <= 0) {
11599         DisplayError(_("Can't back up any further"), 0);
11600         return FALSE;
11601     }
11602     if (cmailMsgLoaded) {
11603         return CmailLoadGame(lastLoadGameFP, gameNumber,
11604                              lastLoadGameTitle, lastLoadGameUseList);
11605     } else {
11606         return LoadGame(lastLoadGameFP, gameNumber,
11607                         lastLoadGameTitle, lastLoadGameUseList);
11608     }
11609 }
11610
11611 int keys[EmptySquare+1];
11612
11613 int
11614 PositionMatches (Board b1, Board b2)
11615 {
11616     int r, f, sum=0;
11617     switch(appData.searchMode) {
11618         case 1: return CompareWithRights(b1, b2);
11619         case 2:
11620             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11621                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11622             }
11623             return TRUE;
11624         case 3:
11625             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11626               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11627                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11628             }
11629             return sum==0;
11630         case 4:
11631             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11632                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11633             }
11634             return sum==0;
11635     }
11636     return TRUE;
11637 }
11638
11639 #define Q_PROMO  4
11640 #define Q_EP     3
11641 #define Q_BCASTL 2
11642 #define Q_WCASTL 1
11643
11644 int pieceList[256], quickBoard[256];
11645 ChessSquare pieceType[256] = { EmptySquare };
11646 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11647 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11648 int soughtTotal, turn;
11649 Boolean epOK, flipSearch;
11650
11651 typedef struct {
11652     unsigned char piece, to;
11653 } Move;
11654
11655 #define DSIZE (250000)
11656
11657 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11658 Move *moveDatabase = initialSpace;
11659 unsigned int movePtr, dataSize = DSIZE;
11660
11661 int
11662 MakePieceList (Board board, int *counts)
11663 {
11664     int r, f, n=Q_PROMO, total=0;
11665     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11666     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11667         int sq = f + (r<<4);
11668         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11669             quickBoard[sq] = ++n;
11670             pieceList[n] = sq;
11671             pieceType[n] = board[r][f];
11672             counts[board[r][f]]++;
11673             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11674             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11675             total++;
11676         }
11677     }
11678     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11679     return total;
11680 }
11681
11682 void
11683 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11684 {
11685     int sq = fromX + (fromY<<4);
11686     int piece = quickBoard[sq];
11687     quickBoard[sq] = 0;
11688     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11689     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11690         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11691         moveDatabase[movePtr++].piece = Q_WCASTL;
11692         quickBoard[sq] = piece;
11693         piece = quickBoard[from]; quickBoard[from] = 0;
11694         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11695     } else
11696     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11697         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11698         moveDatabase[movePtr++].piece = Q_BCASTL;
11699         quickBoard[sq] = piece;
11700         piece = quickBoard[from]; quickBoard[from] = 0;
11701         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11702     } else
11703     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11704         quickBoard[(fromY<<4)+toX] = 0;
11705         moveDatabase[movePtr].piece = Q_EP;
11706         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11707         moveDatabase[movePtr].to = sq;
11708     } else
11709     if(promoPiece != pieceType[piece]) {
11710         moveDatabase[movePtr++].piece = Q_PROMO;
11711         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11712     }
11713     moveDatabase[movePtr].piece = piece;
11714     quickBoard[sq] = piece;
11715     movePtr++;
11716 }
11717
11718 int
11719 PackGame (Board board)
11720 {
11721     Move *newSpace = NULL;
11722     moveDatabase[movePtr].piece = 0; // terminate previous game
11723     if(movePtr > dataSize) {
11724         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11725         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11726         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11727         if(newSpace) {
11728             int i;
11729             Move *p = moveDatabase, *q = newSpace;
11730             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11731             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11732             moveDatabase = newSpace;
11733         } else { // calloc failed, we must be out of memory. Too bad...
11734             dataSize = 0; // prevent calloc events for all subsequent games
11735             return 0;     // and signal this one isn't cached
11736         }
11737     }
11738     movePtr++;
11739     MakePieceList(board, counts);
11740     return movePtr;
11741 }
11742
11743 int
11744 QuickCompare (Board board, int *minCounts, int *maxCounts)
11745 {   // compare according to search mode
11746     int r, f;
11747     switch(appData.searchMode)
11748     {
11749       case 1: // exact position match
11750         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11751         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11752             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11753         }
11754         break;
11755       case 2: // can have extra material on empty squares
11756         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11757             if(board[r][f] == EmptySquare) continue;
11758             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11759         }
11760         break;
11761       case 3: // material with exact Pawn structure
11762         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11763             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11764             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11765         } // fall through to material comparison
11766       case 4: // exact material
11767         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11768         break;
11769       case 6: // material range with given imbalance
11770         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11771         // fall through to range comparison
11772       case 5: // material range
11773         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11774     }
11775     return TRUE;
11776 }
11777
11778 int
11779 QuickScan (Board board, Move *move)
11780 {   // reconstruct game,and compare all positions in it
11781     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11782     do {
11783         int piece = move->piece;
11784         int to = move->to, from = pieceList[piece];
11785         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11786           if(!piece) return -1;
11787           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11788             piece = (++move)->piece;
11789             from = pieceList[piece];
11790             counts[pieceType[piece]]--;
11791             pieceType[piece] = (ChessSquare) move->to;
11792             counts[move->to]++;
11793           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11794             counts[pieceType[quickBoard[to]]]--;
11795             quickBoard[to] = 0; total--;
11796             move++;
11797             continue;
11798           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11799             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11800             from  = pieceList[piece]; // so this must be King
11801             quickBoard[from] = 0;
11802             pieceList[piece] = to;
11803             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11804             quickBoard[from] = 0; // rook
11805             quickBoard[to] = piece;
11806             to = move->to; piece = move->piece;
11807             goto aftercastle;
11808           }
11809         }
11810         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11811         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11812         quickBoard[from] = 0;
11813       aftercastle:
11814         quickBoard[to] = piece;
11815         pieceList[piece] = to;
11816         cnt++; turn ^= 3;
11817         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11818            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11819            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11820                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11821           ) {
11822             static int lastCounts[EmptySquare+1];
11823             int i;
11824             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11825             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11826         } else stretch = 0;
11827         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11828         move++;
11829     } while(1);
11830 }
11831
11832 void
11833 InitSearch ()
11834 {
11835     int r, f;
11836     flipSearch = FALSE;
11837     CopyBoard(soughtBoard, boards[currentMove]);
11838     soughtTotal = MakePieceList(soughtBoard, maxSought);
11839     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11840     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11841     CopyBoard(reverseBoard, boards[currentMove]);
11842     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11843         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11844         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11845         reverseBoard[r][f] = piece;
11846     }
11847     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11848     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11849     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11850                  || (boards[currentMove][CASTLING][2] == NoRights ||
11851                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11852                  && (boards[currentMove][CASTLING][5] == NoRights ||
11853                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11854       ) {
11855         flipSearch = TRUE;
11856         CopyBoard(flipBoard, soughtBoard);
11857         CopyBoard(rotateBoard, reverseBoard);
11858         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11859             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11860             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11861         }
11862     }
11863     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11864     if(appData.searchMode >= 5) {
11865         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11866         MakePieceList(soughtBoard, minSought);
11867         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11868     }
11869     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11870         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11871 }
11872
11873 GameInfo dummyInfo;
11874 static int creatingBook;
11875
11876 int
11877 GameContainsPosition (FILE *f, ListGame *lg)
11878 {
11879     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11880     int fromX, fromY, toX, toY;
11881     char promoChar;
11882     static int initDone=FALSE;
11883
11884     // weed out games based on numerical tag comparison
11885     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11886     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11887     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11888     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11889     if(!initDone) {
11890         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11891         initDone = TRUE;
11892     }
11893     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11894     else CopyBoard(boards[scratch], initialPosition); // default start position
11895     if(lg->moves) {
11896         turn = btm + 1;
11897         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11898         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11899     }
11900     if(btm) plyNr++;
11901     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11902     fseek(f, lg->offset, 0);
11903     yynewfile(f);
11904     while(1) {
11905         yyboardindex = scratch;
11906         quickFlag = plyNr+1;
11907         next = Myylex();
11908         quickFlag = 0;
11909         switch(next) {
11910             case PGNTag:
11911                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11912             default:
11913                 continue;
11914
11915             case XBoardGame:
11916             case GNUChessGame:
11917                 if(plyNr) return -1; // after we have seen moves, this is for new game
11918               continue;
11919
11920             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11921             case ImpossibleMove:
11922             case WhiteWins: // game ends here with these four
11923             case BlackWins:
11924             case GameIsDrawn:
11925             case GameUnfinished:
11926                 return -1;
11927
11928             case IllegalMove:
11929                 if(appData.testLegality) return -1;
11930             case WhiteCapturesEnPassant:
11931             case BlackCapturesEnPassant:
11932             case WhitePromotion:
11933             case BlackPromotion:
11934             case WhiteNonPromotion:
11935             case BlackNonPromotion:
11936             case NormalMove:
11937             case WhiteKingSideCastle:
11938             case WhiteQueenSideCastle:
11939             case BlackKingSideCastle:
11940             case BlackQueenSideCastle:
11941             case WhiteKingSideCastleWild:
11942             case WhiteQueenSideCastleWild:
11943             case BlackKingSideCastleWild:
11944             case BlackQueenSideCastleWild:
11945             case WhiteHSideCastleFR:
11946             case WhiteASideCastleFR:
11947             case BlackHSideCastleFR:
11948             case BlackASideCastleFR:
11949                 fromX = currentMoveString[0] - AAA;
11950                 fromY = currentMoveString[1] - ONE;
11951                 toX = currentMoveString[2] - AAA;
11952                 toY = currentMoveString[3] - ONE;
11953                 promoChar = currentMoveString[4];
11954                 break;
11955             case WhiteDrop:
11956             case BlackDrop:
11957                 fromX = next == WhiteDrop ?
11958                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11959                   (int) CharToPiece(ToLower(currentMoveString[0]));
11960                 fromY = DROP_RANK;
11961                 toX = currentMoveString[2] - AAA;
11962                 toY = currentMoveString[3] - ONE;
11963                 promoChar = 0;
11964                 break;
11965         }
11966         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11967         plyNr++;
11968         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11969         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11970         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11971         if(appData.findMirror) {
11972             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11973             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11974         }
11975     }
11976 }
11977
11978 /* Load the nth game from open file f */
11979 int
11980 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11981 {
11982     ChessMove cm;
11983     char buf[MSG_SIZ];
11984     int gn = gameNumber;
11985     ListGame *lg = NULL;
11986     int numPGNTags = 0;
11987     int err, pos = -1;
11988     GameMode oldGameMode;
11989     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11990
11991     if (appData.debugMode)
11992         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11993
11994     if (gameMode == Training )
11995         SetTrainingModeOff();
11996
11997     oldGameMode = gameMode;
11998     if (gameMode != BeginningOfGame) {
11999       Reset(FALSE, TRUE);
12000     }
12001
12002     gameFileFP = f;
12003     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12004         fclose(lastLoadGameFP);
12005     }
12006
12007     if (useList) {
12008         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12009
12010         if (lg) {
12011             fseek(f, lg->offset, 0);
12012             GameListHighlight(gameNumber);
12013             pos = lg->position;
12014             gn = 1;
12015         }
12016         else {
12017             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12018               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12019             else
12020             DisplayError(_("Game number out of range"), 0);
12021             return FALSE;
12022         }
12023     } else {
12024         GameListDestroy();
12025         if (fseek(f, 0, 0) == -1) {
12026             if (f == lastLoadGameFP ?
12027                 gameNumber == lastLoadGameNumber + 1 :
12028                 gameNumber == 1) {
12029                 gn = 1;
12030             } else {
12031                 DisplayError(_("Can't seek on game file"), 0);
12032                 return FALSE;
12033             }
12034         }
12035     }
12036     lastLoadGameFP = f;
12037     lastLoadGameNumber = gameNumber;
12038     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12039     lastLoadGameUseList = useList;
12040
12041     yynewfile(f);
12042
12043     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12044       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12045                 lg->gameInfo.black);
12046             DisplayTitle(buf);
12047     } else if (*title != NULLCHAR) {
12048         if (gameNumber > 1) {
12049           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12050             DisplayTitle(buf);
12051         } else {
12052             DisplayTitle(title);
12053         }
12054     }
12055
12056     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12057         gameMode = PlayFromGameFile;
12058         ModeHighlight();
12059     }
12060
12061     currentMove = forwardMostMove = backwardMostMove = 0;
12062     CopyBoard(boards[0], initialPosition);
12063     StopClocks();
12064
12065     /*
12066      * Skip the first gn-1 games in the file.
12067      * Also skip over anything that precedes an identifiable
12068      * start of game marker, to avoid being confused by
12069      * garbage at the start of the file.  Currently
12070      * recognized start of game markers are the move number "1",
12071      * the pattern "gnuchess .* game", the pattern
12072      * "^[#;%] [^ ]* game file", and a PGN tag block.
12073      * A game that starts with one of the latter two patterns
12074      * will also have a move number 1, possibly
12075      * following a position diagram.
12076      * 5-4-02: Let's try being more lenient and allowing a game to
12077      * start with an unnumbered move.  Does that break anything?
12078      */
12079     cm = lastLoadGameStart = EndOfFile;
12080     while (gn > 0) {
12081         yyboardindex = forwardMostMove;
12082         cm = (ChessMove) Myylex();
12083         switch (cm) {
12084           case EndOfFile:
12085             if (cmailMsgLoaded) {
12086                 nCmailGames = CMAIL_MAX_GAMES - gn;
12087             } else {
12088                 Reset(TRUE, TRUE);
12089                 DisplayError(_("Game not found in file"), 0);
12090             }
12091             return FALSE;
12092
12093           case GNUChessGame:
12094           case XBoardGame:
12095             gn--;
12096             lastLoadGameStart = cm;
12097             break;
12098
12099           case MoveNumberOne:
12100             switch (lastLoadGameStart) {
12101               case GNUChessGame:
12102               case XBoardGame:
12103               case PGNTag:
12104                 break;
12105               case MoveNumberOne:
12106               case EndOfFile:
12107                 gn--;           /* count this game */
12108                 lastLoadGameStart = cm;
12109                 break;
12110               default:
12111                 /* impossible */
12112                 break;
12113             }
12114             break;
12115
12116           case PGNTag:
12117             switch (lastLoadGameStart) {
12118               case GNUChessGame:
12119               case PGNTag:
12120               case MoveNumberOne:
12121               case EndOfFile:
12122                 gn--;           /* count this game */
12123                 lastLoadGameStart = cm;
12124                 break;
12125               case XBoardGame:
12126                 lastLoadGameStart = cm; /* game counted already */
12127                 break;
12128               default:
12129                 /* impossible */
12130                 break;
12131             }
12132             if (gn > 0) {
12133                 do {
12134                     yyboardindex = forwardMostMove;
12135                     cm = (ChessMove) Myylex();
12136                 } while (cm == PGNTag || cm == Comment);
12137             }
12138             break;
12139
12140           case WhiteWins:
12141           case BlackWins:
12142           case GameIsDrawn:
12143             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12144                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12145                     != CMAIL_OLD_RESULT) {
12146                     nCmailResults ++ ;
12147                     cmailResult[  CMAIL_MAX_GAMES
12148                                 - gn - 1] = CMAIL_OLD_RESULT;
12149                 }
12150             }
12151             break;
12152
12153           case NormalMove:
12154             /* Only a NormalMove can be at the start of a game
12155              * without a position diagram. */
12156             if (lastLoadGameStart == EndOfFile ) {
12157               gn--;
12158               lastLoadGameStart = MoveNumberOne;
12159             }
12160             break;
12161
12162           default:
12163             break;
12164         }
12165     }
12166
12167     if (appData.debugMode)
12168       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12169
12170     if (cm == XBoardGame) {
12171         /* Skip any header junk before position diagram and/or move 1 */
12172         for (;;) {
12173             yyboardindex = forwardMostMove;
12174             cm = (ChessMove) Myylex();
12175
12176             if (cm == EndOfFile ||
12177                 cm == GNUChessGame || cm == XBoardGame) {
12178                 /* Empty game; pretend end-of-file and handle later */
12179                 cm = EndOfFile;
12180                 break;
12181             }
12182
12183             if (cm == MoveNumberOne || cm == PositionDiagram ||
12184                 cm == PGNTag || cm == Comment)
12185               break;
12186         }
12187     } else if (cm == GNUChessGame) {
12188         if (gameInfo.event != NULL) {
12189             free(gameInfo.event);
12190         }
12191         gameInfo.event = StrSave(yy_text);
12192     }
12193
12194     startedFromSetupPosition = FALSE;
12195     while (cm == PGNTag) {
12196         if (appData.debugMode)
12197           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12198         err = ParsePGNTag(yy_text, &gameInfo);
12199         if (!err) numPGNTags++;
12200
12201         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12202         if(gameInfo.variant != oldVariant) {
12203             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12204             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12205             InitPosition(TRUE);
12206             oldVariant = gameInfo.variant;
12207             if (appData.debugMode)
12208               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12209         }
12210
12211
12212         if (gameInfo.fen != NULL) {
12213           Board initial_position;
12214           startedFromSetupPosition = TRUE;
12215           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12216             Reset(TRUE, TRUE);
12217             DisplayError(_("Bad FEN position in file"), 0);
12218             return FALSE;
12219           }
12220           CopyBoard(boards[0], initial_position);
12221           if (blackPlaysFirst) {
12222             currentMove = forwardMostMove = backwardMostMove = 1;
12223             CopyBoard(boards[1], initial_position);
12224             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12225             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12226             timeRemaining[0][1] = whiteTimeRemaining;
12227             timeRemaining[1][1] = blackTimeRemaining;
12228             if (commentList[0] != NULL) {
12229               commentList[1] = commentList[0];
12230               commentList[0] = NULL;
12231             }
12232           } else {
12233             currentMove = forwardMostMove = backwardMostMove = 0;
12234           }
12235           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12236           {   int i;
12237               initialRulePlies = FENrulePlies;
12238               for( i=0; i< nrCastlingRights; i++ )
12239                   initialRights[i] = initial_position[CASTLING][i];
12240           }
12241           yyboardindex = forwardMostMove;
12242           free(gameInfo.fen);
12243           gameInfo.fen = NULL;
12244         }
12245
12246         yyboardindex = forwardMostMove;
12247         cm = (ChessMove) Myylex();
12248
12249         /* Handle comments interspersed among the tags */
12250         while (cm == Comment) {
12251             char *p;
12252             if (appData.debugMode)
12253               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12254             p = yy_text;
12255             AppendComment(currentMove, p, FALSE);
12256             yyboardindex = forwardMostMove;
12257             cm = (ChessMove) Myylex();
12258         }
12259     }
12260
12261     /* don't rely on existence of Event tag since if game was
12262      * pasted from clipboard the Event tag may not exist
12263      */
12264     if (numPGNTags > 0){
12265         char *tags;
12266         if (gameInfo.variant == VariantNormal) {
12267           VariantClass v = StringToVariant(gameInfo.event);
12268           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12269           if(v < VariantShogi) gameInfo.variant = v;
12270         }
12271         if (!matchMode) {
12272           if( appData.autoDisplayTags ) {
12273             tags = PGNTags(&gameInfo);
12274             TagsPopUp(tags, CmailMsg());
12275             free(tags);
12276           }
12277         }
12278     } else {
12279         /* Make something up, but don't display it now */
12280         SetGameInfo();
12281         TagsPopDown();
12282     }
12283
12284     if (cm == PositionDiagram) {
12285         int i, j;
12286         char *p;
12287         Board initial_position;
12288
12289         if (appData.debugMode)
12290           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12291
12292         if (!startedFromSetupPosition) {
12293             p = yy_text;
12294             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12295               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12296                 switch (*p) {
12297                   case '{':
12298                   case '[':
12299                   case '-':
12300                   case ' ':
12301                   case '\t':
12302                   case '\n':
12303                   case '\r':
12304                     break;
12305                   default:
12306                     initial_position[i][j++] = CharToPiece(*p);
12307                     break;
12308                 }
12309             while (*p == ' ' || *p == '\t' ||
12310                    *p == '\n' || *p == '\r') p++;
12311
12312             if (strncmp(p, "black", strlen("black"))==0)
12313               blackPlaysFirst = TRUE;
12314             else
12315               blackPlaysFirst = FALSE;
12316             startedFromSetupPosition = TRUE;
12317
12318             CopyBoard(boards[0], initial_position);
12319             if (blackPlaysFirst) {
12320                 currentMove = forwardMostMove = backwardMostMove = 1;
12321                 CopyBoard(boards[1], initial_position);
12322                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12323                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12324                 timeRemaining[0][1] = whiteTimeRemaining;
12325                 timeRemaining[1][1] = blackTimeRemaining;
12326                 if (commentList[0] != NULL) {
12327                     commentList[1] = commentList[0];
12328                     commentList[0] = NULL;
12329                 }
12330             } else {
12331                 currentMove = forwardMostMove = backwardMostMove = 0;
12332             }
12333         }
12334         yyboardindex = forwardMostMove;
12335         cm = (ChessMove) Myylex();
12336     }
12337
12338   if(!creatingBook) {
12339     if (first.pr == NoProc) {
12340         StartChessProgram(&first);
12341     }
12342     InitChessProgram(&first, FALSE);
12343     SendToProgram("force\n", &first);
12344     if (startedFromSetupPosition) {
12345         SendBoard(&first, forwardMostMove);
12346     if (appData.debugMode) {
12347         fprintf(debugFP, "Load Game\n");
12348     }
12349         DisplayBothClocks();
12350     }
12351   }
12352
12353     /* [HGM] server: flag to write setup moves in broadcast file as one */
12354     loadFlag = appData.suppressLoadMoves;
12355
12356     while (cm == Comment) {
12357         char *p;
12358         if (appData.debugMode)
12359           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12360         p = yy_text;
12361         AppendComment(currentMove, p, FALSE);
12362         yyboardindex = forwardMostMove;
12363         cm = (ChessMove) Myylex();
12364     }
12365
12366     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12367         cm == WhiteWins || cm == BlackWins ||
12368         cm == GameIsDrawn || cm == GameUnfinished) {
12369         DisplayMessage("", _("No moves in game"));
12370         if (cmailMsgLoaded) {
12371             if (appData.debugMode)
12372               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12373             ClearHighlights();
12374             flipView = FALSE;
12375         }
12376         DrawPosition(FALSE, boards[currentMove]);
12377         DisplayBothClocks();
12378         gameMode = EditGame;
12379         ModeHighlight();
12380         gameFileFP = NULL;
12381         cmailOldMove = 0;
12382         return TRUE;
12383     }
12384
12385     // [HGM] PV info: routine tests if comment empty
12386     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12387         DisplayComment(currentMove - 1, commentList[currentMove]);
12388     }
12389     if (!matchMode && appData.timeDelay != 0)
12390       DrawPosition(FALSE, boards[currentMove]);
12391
12392     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12393       programStats.ok_to_send = 1;
12394     }
12395
12396     /* if the first token after the PGN tags is a move
12397      * and not move number 1, retrieve it from the parser
12398      */
12399     if (cm != MoveNumberOne)
12400         LoadGameOneMove(cm);
12401
12402     /* load the remaining moves from the file */
12403     while (LoadGameOneMove(EndOfFile)) {
12404       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12405       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12406     }
12407
12408     /* rewind to the start of the game */
12409     currentMove = backwardMostMove;
12410
12411     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12412
12413     if (oldGameMode == AnalyzeFile ||
12414         oldGameMode == AnalyzeMode) {
12415       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12416       AnalyzeFileEvent();
12417     }
12418
12419     if(creatingBook) return TRUE;
12420     if (!matchMode && pos > 0) {
12421         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12422     } else
12423     if (matchMode || appData.timeDelay == 0) {
12424       ToEndEvent();
12425     } else if (appData.timeDelay > 0) {
12426       AutoPlayGameLoop();
12427     }
12428
12429     if (appData.debugMode)
12430         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12431
12432     loadFlag = 0; /* [HGM] true game starts */
12433     return TRUE;
12434 }
12435
12436 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12437 int
12438 ReloadPosition (int offset)
12439 {
12440     int positionNumber = lastLoadPositionNumber + offset;
12441     if (lastLoadPositionFP == NULL) {
12442         DisplayError(_("No position has been loaded yet"), 0);
12443         return FALSE;
12444     }
12445     if (positionNumber <= 0) {
12446         DisplayError(_("Can't back up any further"), 0);
12447         return FALSE;
12448     }
12449     return LoadPosition(lastLoadPositionFP, positionNumber,
12450                         lastLoadPositionTitle);
12451 }
12452
12453 /* Load the nth position from the given file */
12454 int
12455 LoadPositionFromFile (char *filename, int n, char *title)
12456 {
12457     FILE *f;
12458     char buf[MSG_SIZ];
12459
12460     if (strcmp(filename, "-") == 0) {
12461         return LoadPosition(stdin, n, "stdin");
12462     } else {
12463         f = fopen(filename, "rb");
12464         if (f == NULL) {
12465             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12466             DisplayError(buf, errno);
12467             return FALSE;
12468         } else {
12469             return LoadPosition(f, n, title);
12470         }
12471     }
12472 }
12473
12474 /* Load the nth position from the given open file, and close it */
12475 int
12476 LoadPosition (FILE *f, int positionNumber, char *title)
12477 {
12478     char *p, line[MSG_SIZ];
12479     Board initial_position;
12480     int i, j, fenMode, pn;
12481
12482     if (gameMode == Training )
12483         SetTrainingModeOff();
12484
12485     if (gameMode != BeginningOfGame) {
12486         Reset(FALSE, TRUE);
12487     }
12488     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12489         fclose(lastLoadPositionFP);
12490     }
12491     if (positionNumber == 0) positionNumber = 1;
12492     lastLoadPositionFP = f;
12493     lastLoadPositionNumber = positionNumber;
12494     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12495     if (first.pr == NoProc && !appData.noChessProgram) {
12496       StartChessProgram(&first);
12497       InitChessProgram(&first, FALSE);
12498     }
12499     pn = positionNumber;
12500     if (positionNumber < 0) {
12501         /* Negative position number means to seek to that byte offset */
12502         if (fseek(f, -positionNumber, 0) == -1) {
12503             DisplayError(_("Can't seek on position file"), 0);
12504             return FALSE;
12505         };
12506         pn = 1;
12507     } else {
12508         if (fseek(f, 0, 0) == -1) {
12509             if (f == lastLoadPositionFP ?
12510                 positionNumber == lastLoadPositionNumber + 1 :
12511                 positionNumber == 1) {
12512                 pn = 1;
12513             } else {
12514                 DisplayError(_("Can't seek on position file"), 0);
12515                 return FALSE;
12516             }
12517         }
12518     }
12519     /* See if this file is FEN or old-style xboard */
12520     if (fgets(line, MSG_SIZ, f) == NULL) {
12521         DisplayError(_("Position not found in file"), 0);
12522         return FALSE;
12523     }
12524     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12525     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12526
12527     if (pn >= 2) {
12528         if (fenMode || line[0] == '#') pn--;
12529         while (pn > 0) {
12530             /* skip positions before number pn */
12531             if (fgets(line, MSG_SIZ, f) == NULL) {
12532                 Reset(TRUE, TRUE);
12533                 DisplayError(_("Position not found in file"), 0);
12534                 return FALSE;
12535             }
12536             if (fenMode || line[0] == '#') pn--;
12537         }
12538     }
12539
12540     if (fenMode) {
12541         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12542             DisplayError(_("Bad FEN position in file"), 0);
12543             return FALSE;
12544         }
12545     } else {
12546         (void) fgets(line, MSG_SIZ, f);
12547         (void) fgets(line, MSG_SIZ, f);
12548
12549         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12550             (void) fgets(line, MSG_SIZ, f);
12551             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12552                 if (*p == ' ')
12553                   continue;
12554                 initial_position[i][j++] = CharToPiece(*p);
12555             }
12556         }
12557
12558         blackPlaysFirst = FALSE;
12559         if (!feof(f)) {
12560             (void) fgets(line, MSG_SIZ, f);
12561             if (strncmp(line, "black", strlen("black"))==0)
12562               blackPlaysFirst = TRUE;
12563         }
12564     }
12565     startedFromSetupPosition = TRUE;
12566
12567     CopyBoard(boards[0], initial_position);
12568     if (blackPlaysFirst) {
12569         currentMove = forwardMostMove = backwardMostMove = 1;
12570         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12571         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12572         CopyBoard(boards[1], initial_position);
12573         DisplayMessage("", _("Black to play"));
12574     } else {
12575         currentMove = forwardMostMove = backwardMostMove = 0;
12576         DisplayMessage("", _("White to play"));
12577     }
12578     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12579     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12580         SendToProgram("force\n", &first);
12581         SendBoard(&first, forwardMostMove);
12582     }
12583     if (appData.debugMode) {
12584 int i, j;
12585   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12586   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12587         fprintf(debugFP, "Load Position\n");
12588     }
12589
12590     if (positionNumber > 1) {
12591       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12592         DisplayTitle(line);
12593     } else {
12594         DisplayTitle(title);
12595     }
12596     gameMode = EditGame;
12597     ModeHighlight();
12598     ResetClocks();
12599     timeRemaining[0][1] = whiteTimeRemaining;
12600     timeRemaining[1][1] = blackTimeRemaining;
12601     DrawPosition(FALSE, boards[currentMove]);
12602
12603     return TRUE;
12604 }
12605
12606
12607 void
12608 CopyPlayerNameIntoFileName (char **dest, char *src)
12609 {
12610     while (*src != NULLCHAR && *src != ',') {
12611         if (*src == ' ') {
12612             *(*dest)++ = '_';
12613             src++;
12614         } else {
12615             *(*dest)++ = *src++;
12616         }
12617     }
12618 }
12619
12620 char *
12621 DefaultFileName (char *ext)
12622 {
12623     static char def[MSG_SIZ];
12624     char *p;
12625
12626     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12627         p = def;
12628         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12629         *p++ = '-';
12630         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12631         *p++ = '.';
12632         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12633     } else {
12634         def[0] = NULLCHAR;
12635     }
12636     return def;
12637 }
12638
12639 /* Save the current game to the given file */
12640 int
12641 SaveGameToFile (char *filename, int append)
12642 {
12643     FILE *f;
12644     char buf[MSG_SIZ];
12645     int result, i, t,tot=0;
12646
12647     if (strcmp(filename, "-") == 0) {
12648         return SaveGame(stdout, 0, NULL);
12649     } else {
12650         for(i=0; i<10; i++) { // upto 10 tries
12651              f = fopen(filename, append ? "a" : "w");
12652              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12653              if(f || errno != 13) break;
12654              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12655              tot += t;
12656         }
12657         if (f == NULL) {
12658             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12659             DisplayError(buf, errno);
12660             return FALSE;
12661         } else {
12662             safeStrCpy(buf, lastMsg, MSG_SIZ);
12663             DisplayMessage(_("Waiting for access to save file"), "");
12664             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12665             DisplayMessage(_("Saving game"), "");
12666             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12667             result = SaveGame(f, 0, NULL);
12668             DisplayMessage(buf, "");
12669             return result;
12670         }
12671     }
12672 }
12673
12674 char *
12675 SavePart (char *str)
12676 {
12677     static char buf[MSG_SIZ];
12678     char *p;
12679
12680     p = strchr(str, ' ');
12681     if (p == NULL) return str;
12682     strncpy(buf, str, p - str);
12683     buf[p - str] = NULLCHAR;
12684     return buf;
12685 }
12686
12687 #define PGN_MAX_LINE 75
12688
12689 #define PGN_SIDE_WHITE  0
12690 #define PGN_SIDE_BLACK  1
12691
12692 static int
12693 FindFirstMoveOutOfBook (int side)
12694 {
12695     int result = -1;
12696
12697     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12698         int index = backwardMostMove;
12699         int has_book_hit = 0;
12700
12701         if( (index % 2) != side ) {
12702             index++;
12703         }
12704
12705         while( index < forwardMostMove ) {
12706             /* Check to see if engine is in book */
12707             int depth = pvInfoList[index].depth;
12708             int score = pvInfoList[index].score;
12709             int in_book = 0;
12710
12711             if( depth <= 2 ) {
12712                 in_book = 1;
12713             }
12714             else if( score == 0 && depth == 63 ) {
12715                 in_book = 1; /* Zappa */
12716             }
12717             else if( score == 2 && depth == 99 ) {
12718                 in_book = 1; /* Abrok */
12719             }
12720
12721             has_book_hit += in_book;
12722
12723             if( ! in_book ) {
12724                 result = index;
12725
12726                 break;
12727             }
12728
12729             index += 2;
12730         }
12731     }
12732
12733     return result;
12734 }
12735
12736 void
12737 GetOutOfBookInfo (char * buf)
12738 {
12739     int oob[2];
12740     int i;
12741     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12742
12743     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12744     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12745
12746     *buf = '\0';
12747
12748     if( oob[0] >= 0 || oob[1] >= 0 ) {
12749         for( i=0; i<2; i++ ) {
12750             int idx = oob[i];
12751
12752             if( idx >= 0 ) {
12753                 if( i > 0 && oob[0] >= 0 ) {
12754                     strcat( buf, "   " );
12755                 }
12756
12757                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12758                 sprintf( buf+strlen(buf), "%s%.2f",
12759                     pvInfoList[idx].score >= 0 ? "+" : "",
12760                     pvInfoList[idx].score / 100.0 );
12761             }
12762         }
12763     }
12764 }
12765
12766 /* Save game in PGN style and close the file */
12767 int
12768 SaveGamePGN (FILE *f)
12769 {
12770     int i, offset, linelen, newblock;
12771 //    char *movetext;
12772     char numtext[32];
12773     int movelen, numlen, blank;
12774     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12775
12776     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12777
12778     PrintPGNTags(f, &gameInfo);
12779
12780     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12781
12782     if (backwardMostMove > 0 || startedFromSetupPosition) {
12783         char *fen = PositionToFEN(backwardMostMove, NULL);
12784         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12785         fprintf(f, "\n{--------------\n");
12786         PrintPosition(f, backwardMostMove);
12787         fprintf(f, "--------------}\n");
12788         free(fen);
12789     }
12790     else {
12791         /* [AS] Out of book annotation */
12792         if( appData.saveOutOfBookInfo ) {
12793             char buf[64];
12794
12795             GetOutOfBookInfo( buf );
12796
12797             if( buf[0] != '\0' ) {
12798                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12799             }
12800         }
12801
12802         fprintf(f, "\n");
12803     }
12804
12805     i = backwardMostMove;
12806     linelen = 0;
12807     newblock = TRUE;
12808
12809     while (i < forwardMostMove) {
12810         /* Print comments preceding this move */
12811         if (commentList[i] != NULL) {
12812             if (linelen > 0) fprintf(f, "\n");
12813             fprintf(f, "%s", commentList[i]);
12814             linelen = 0;
12815             newblock = TRUE;
12816         }
12817
12818         /* Format move number */
12819         if ((i % 2) == 0)
12820           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12821         else
12822           if (newblock)
12823             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12824           else
12825             numtext[0] = NULLCHAR;
12826
12827         numlen = strlen(numtext);
12828         newblock = FALSE;
12829
12830         /* Print move number */
12831         blank = linelen > 0 && numlen > 0;
12832         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12833             fprintf(f, "\n");
12834             linelen = 0;
12835             blank = 0;
12836         }
12837         if (blank) {
12838             fprintf(f, " ");
12839             linelen++;
12840         }
12841         fprintf(f, "%s", numtext);
12842         linelen += numlen;
12843
12844         /* Get move */
12845         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12846         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12847
12848         /* Print move */
12849         blank = linelen > 0 && movelen > 0;
12850         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12851             fprintf(f, "\n");
12852             linelen = 0;
12853             blank = 0;
12854         }
12855         if (blank) {
12856             fprintf(f, " ");
12857             linelen++;
12858         }
12859         fprintf(f, "%s", move_buffer);
12860         linelen += movelen;
12861
12862         /* [AS] Add PV info if present */
12863         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12864             /* [HGM] add time */
12865             char buf[MSG_SIZ]; int seconds;
12866
12867             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12868
12869             if( seconds <= 0)
12870               buf[0] = 0;
12871             else
12872               if( seconds < 30 )
12873                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12874               else
12875                 {
12876                   seconds = (seconds + 4)/10; // round to full seconds
12877                   if( seconds < 60 )
12878                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12879                   else
12880                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12881                 }
12882
12883             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12884                       pvInfoList[i].score >= 0 ? "+" : "",
12885                       pvInfoList[i].score / 100.0,
12886                       pvInfoList[i].depth,
12887                       buf );
12888
12889             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12890
12891             /* Print score/depth */
12892             blank = linelen > 0 && movelen > 0;
12893             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12894                 fprintf(f, "\n");
12895                 linelen = 0;
12896                 blank = 0;
12897             }
12898             if (blank) {
12899                 fprintf(f, " ");
12900                 linelen++;
12901             }
12902             fprintf(f, "%s", move_buffer);
12903             linelen += movelen;
12904         }
12905
12906         i++;
12907     }
12908
12909     /* Start a new line */
12910     if (linelen > 0) fprintf(f, "\n");
12911
12912     /* Print comments after last move */
12913     if (commentList[i] != NULL) {
12914         fprintf(f, "%s\n", commentList[i]);
12915     }
12916
12917     /* Print result */
12918     if (gameInfo.resultDetails != NULL &&
12919         gameInfo.resultDetails[0] != NULLCHAR) {
12920         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12921                 PGNResult(gameInfo.result));
12922     } else {
12923         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12924     }
12925
12926     fclose(f);
12927     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12928     return TRUE;
12929 }
12930
12931 /* Save game in old style and close the file */
12932 int
12933 SaveGameOldStyle (FILE *f)
12934 {
12935     int i, offset;
12936     time_t tm;
12937
12938     tm = time((time_t *) NULL);
12939
12940     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12941     PrintOpponents(f);
12942
12943     if (backwardMostMove > 0 || startedFromSetupPosition) {
12944         fprintf(f, "\n[--------------\n");
12945         PrintPosition(f, backwardMostMove);
12946         fprintf(f, "--------------]\n");
12947     } else {
12948         fprintf(f, "\n");
12949     }
12950
12951     i = backwardMostMove;
12952     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12953
12954     while (i < forwardMostMove) {
12955         if (commentList[i] != NULL) {
12956             fprintf(f, "[%s]\n", commentList[i]);
12957         }
12958
12959         if ((i % 2) == 1) {
12960             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12961             i++;
12962         } else {
12963             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12964             i++;
12965             if (commentList[i] != NULL) {
12966                 fprintf(f, "\n");
12967                 continue;
12968             }
12969             if (i >= forwardMostMove) {
12970                 fprintf(f, "\n");
12971                 break;
12972             }
12973             fprintf(f, "%s\n", parseList[i]);
12974             i++;
12975         }
12976     }
12977
12978     if (commentList[i] != NULL) {
12979         fprintf(f, "[%s]\n", commentList[i]);
12980     }
12981
12982     /* This isn't really the old style, but it's close enough */
12983     if (gameInfo.resultDetails != NULL &&
12984         gameInfo.resultDetails[0] != NULLCHAR) {
12985         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12986                 gameInfo.resultDetails);
12987     } else {
12988         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12989     }
12990
12991     fclose(f);
12992     return TRUE;
12993 }
12994
12995 /* Save the current game to open file f and close the file */
12996 int
12997 SaveGame (FILE *f, int dummy, char *dummy2)
12998 {
12999     if (gameMode == EditPosition) EditPositionDone(TRUE);
13000     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13001     if (appData.oldSaveStyle)
13002       return SaveGameOldStyle(f);
13003     else
13004       return SaveGamePGN(f);
13005 }
13006
13007 /* Save the current position to the given file */
13008 int
13009 SavePositionToFile (char *filename)
13010 {
13011     FILE *f;
13012     char buf[MSG_SIZ];
13013
13014     if (strcmp(filename, "-") == 0) {
13015         return SavePosition(stdout, 0, NULL);
13016     } else {
13017         f = fopen(filename, "a");
13018         if (f == NULL) {
13019             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13020             DisplayError(buf, errno);
13021             return FALSE;
13022         } else {
13023             safeStrCpy(buf, lastMsg, MSG_SIZ);
13024             DisplayMessage(_("Waiting for access to save file"), "");
13025             flock(fileno(f), LOCK_EX); // [HGM] lock
13026             DisplayMessage(_("Saving position"), "");
13027             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13028             SavePosition(f, 0, NULL);
13029             DisplayMessage(buf, "");
13030             return TRUE;
13031         }
13032     }
13033 }
13034
13035 /* Save the current position to the given open file and close the file */
13036 int
13037 SavePosition (FILE *f, int dummy, char *dummy2)
13038 {
13039     time_t tm;
13040     char *fen;
13041
13042     if (gameMode == EditPosition) EditPositionDone(TRUE);
13043     if (appData.oldSaveStyle) {
13044         tm = time((time_t *) NULL);
13045
13046         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13047         PrintOpponents(f);
13048         fprintf(f, "[--------------\n");
13049         PrintPosition(f, currentMove);
13050         fprintf(f, "--------------]\n");
13051     } else {
13052         fen = PositionToFEN(currentMove, NULL);
13053         fprintf(f, "%s\n", fen);
13054         free(fen);
13055     }
13056     fclose(f);
13057     return TRUE;
13058 }
13059
13060 void
13061 ReloadCmailMsgEvent (int unregister)
13062 {
13063 #if !WIN32
13064     static char *inFilename = NULL;
13065     static char *outFilename;
13066     int i;
13067     struct stat inbuf, outbuf;
13068     int status;
13069
13070     /* Any registered moves are unregistered if unregister is set, */
13071     /* i.e. invoked by the signal handler */
13072     if (unregister) {
13073         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13074             cmailMoveRegistered[i] = FALSE;
13075             if (cmailCommentList[i] != NULL) {
13076                 free(cmailCommentList[i]);
13077                 cmailCommentList[i] = NULL;
13078             }
13079         }
13080         nCmailMovesRegistered = 0;
13081     }
13082
13083     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13084         cmailResult[i] = CMAIL_NOT_RESULT;
13085     }
13086     nCmailResults = 0;
13087
13088     if (inFilename == NULL) {
13089         /* Because the filenames are static they only get malloced once  */
13090         /* and they never get freed                                      */
13091         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13092         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13093
13094         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13095         sprintf(outFilename, "%s.out", appData.cmailGameName);
13096     }
13097
13098     status = stat(outFilename, &outbuf);
13099     if (status < 0) {
13100         cmailMailedMove = FALSE;
13101     } else {
13102         status = stat(inFilename, &inbuf);
13103         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13104     }
13105
13106     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13107        counts the games, notes how each one terminated, etc.
13108
13109        It would be nice to remove this kludge and instead gather all
13110        the information while building the game list.  (And to keep it
13111        in the game list nodes instead of having a bunch of fixed-size
13112        parallel arrays.)  Note this will require getting each game's
13113        termination from the PGN tags, as the game list builder does
13114        not process the game moves.  --mann
13115        */
13116     cmailMsgLoaded = TRUE;
13117     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13118
13119     /* Load first game in the file or popup game menu */
13120     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13121
13122 #endif /* !WIN32 */
13123     return;
13124 }
13125
13126 int
13127 RegisterMove ()
13128 {
13129     FILE *f;
13130     char string[MSG_SIZ];
13131
13132     if (   cmailMailedMove
13133         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13134         return TRUE;            /* Allow free viewing  */
13135     }
13136
13137     /* Unregister move to ensure that we don't leave RegisterMove        */
13138     /* with the move registered when the conditions for registering no   */
13139     /* longer hold                                                       */
13140     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13141         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13142         nCmailMovesRegistered --;
13143
13144         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13145           {
13146               free(cmailCommentList[lastLoadGameNumber - 1]);
13147               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13148           }
13149     }
13150
13151     if (cmailOldMove == -1) {
13152         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13153         return FALSE;
13154     }
13155
13156     if (currentMove > cmailOldMove + 1) {
13157         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13158         return FALSE;
13159     }
13160
13161     if (currentMove < cmailOldMove) {
13162         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13163         return FALSE;
13164     }
13165
13166     if (forwardMostMove > currentMove) {
13167         /* Silently truncate extra moves */
13168         TruncateGame();
13169     }
13170
13171     if (   (currentMove == cmailOldMove + 1)
13172         || (   (currentMove == cmailOldMove)
13173             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13174                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13175         if (gameInfo.result != GameUnfinished) {
13176             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13177         }
13178
13179         if (commentList[currentMove] != NULL) {
13180             cmailCommentList[lastLoadGameNumber - 1]
13181               = StrSave(commentList[currentMove]);
13182         }
13183         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13184
13185         if (appData.debugMode)
13186           fprintf(debugFP, "Saving %s for game %d\n",
13187                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13188
13189         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13190
13191         f = fopen(string, "w");
13192         if (appData.oldSaveStyle) {
13193             SaveGameOldStyle(f); /* also closes the file */
13194
13195             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13196             f = fopen(string, "w");
13197             SavePosition(f, 0, NULL); /* also closes the file */
13198         } else {
13199             fprintf(f, "{--------------\n");
13200             PrintPosition(f, currentMove);
13201             fprintf(f, "--------------}\n\n");
13202
13203             SaveGame(f, 0, NULL); /* also closes the file*/
13204         }
13205
13206         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13207         nCmailMovesRegistered ++;
13208     } else if (nCmailGames == 1) {
13209         DisplayError(_("You have not made a move yet"), 0);
13210         return FALSE;
13211     }
13212
13213     return TRUE;
13214 }
13215
13216 void
13217 MailMoveEvent ()
13218 {
13219 #if !WIN32
13220     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13221     FILE *commandOutput;
13222     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13223     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13224     int nBuffers;
13225     int i;
13226     int archived;
13227     char *arcDir;
13228
13229     if (! cmailMsgLoaded) {
13230         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13231         return;
13232     }
13233
13234     if (nCmailGames == nCmailResults) {
13235         DisplayError(_("No unfinished games"), 0);
13236         return;
13237     }
13238
13239 #if CMAIL_PROHIBIT_REMAIL
13240     if (cmailMailedMove) {
13241       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);
13242         DisplayError(msg, 0);
13243         return;
13244     }
13245 #endif
13246
13247     if (! (cmailMailedMove || RegisterMove())) return;
13248
13249     if (   cmailMailedMove
13250         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13251       snprintf(string, MSG_SIZ, partCommandString,
13252                appData.debugMode ? " -v" : "", appData.cmailGameName);
13253         commandOutput = popen(string, "r");
13254
13255         if (commandOutput == NULL) {
13256             DisplayError(_("Failed to invoke cmail"), 0);
13257         } else {
13258             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13259                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13260             }
13261             if (nBuffers > 1) {
13262                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13263                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13264                 nBytes = MSG_SIZ - 1;
13265             } else {
13266                 (void) memcpy(msg, buffer, nBytes);
13267             }
13268             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13269
13270             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13271                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13272
13273                 archived = TRUE;
13274                 for (i = 0; i < nCmailGames; i ++) {
13275                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13276                         archived = FALSE;
13277                     }
13278                 }
13279                 if (   archived
13280                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13281                         != NULL)) {
13282                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13283                            arcDir,
13284                            appData.cmailGameName,
13285                            gameInfo.date);
13286                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13287                     cmailMsgLoaded = FALSE;
13288                 }
13289             }
13290
13291             DisplayInformation(msg);
13292             pclose(commandOutput);
13293         }
13294     } else {
13295         if ((*cmailMsg) != '\0') {
13296             DisplayInformation(cmailMsg);
13297         }
13298     }
13299
13300     return;
13301 #endif /* !WIN32 */
13302 }
13303
13304 char *
13305 CmailMsg ()
13306 {
13307 #if WIN32
13308     return NULL;
13309 #else
13310     int  prependComma = 0;
13311     char number[5];
13312     char string[MSG_SIZ];       /* Space for game-list */
13313     int  i;
13314
13315     if (!cmailMsgLoaded) return "";
13316
13317     if (cmailMailedMove) {
13318       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13319     } else {
13320         /* Create a list of games left */
13321       snprintf(string, MSG_SIZ, "[");
13322         for (i = 0; i < nCmailGames; i ++) {
13323             if (! (   cmailMoveRegistered[i]
13324                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13325                 if (prependComma) {
13326                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13327                 } else {
13328                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13329                     prependComma = 1;
13330                 }
13331
13332                 strcat(string, number);
13333             }
13334         }
13335         strcat(string, "]");
13336
13337         if (nCmailMovesRegistered + nCmailResults == 0) {
13338             switch (nCmailGames) {
13339               case 1:
13340                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13341                 break;
13342
13343               case 2:
13344                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13345                 break;
13346
13347               default:
13348                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13349                          nCmailGames);
13350                 break;
13351             }
13352         } else {
13353             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13354               case 1:
13355                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13356                          string);
13357                 break;
13358
13359               case 0:
13360                 if (nCmailResults == nCmailGames) {
13361                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13362                 } else {
13363                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13364                 }
13365                 break;
13366
13367               default:
13368                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13369                          string);
13370             }
13371         }
13372     }
13373     return cmailMsg;
13374 #endif /* WIN32 */
13375 }
13376
13377 void
13378 ResetGameEvent ()
13379 {
13380     if (gameMode == Training)
13381       SetTrainingModeOff();
13382
13383     Reset(TRUE, TRUE);
13384     cmailMsgLoaded = FALSE;
13385     if (appData.icsActive) {
13386       SendToICS(ics_prefix);
13387       SendToICS("refresh\n");
13388     }
13389 }
13390
13391 void
13392 ExitEvent (int status)
13393 {
13394     exiting++;
13395     if (exiting > 2) {
13396       /* Give up on clean exit */
13397       exit(status);
13398     }
13399     if (exiting > 1) {
13400       /* Keep trying for clean exit */
13401       return;
13402     }
13403
13404     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13405
13406     if (telnetISR != NULL) {
13407       RemoveInputSource(telnetISR);
13408     }
13409     if (icsPR != NoProc) {
13410       DestroyChildProcess(icsPR, TRUE);
13411     }
13412
13413     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13414     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13415
13416     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13417     /* make sure this other one finishes before killing it!                  */
13418     if(endingGame) { int count = 0;
13419         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13420         while(endingGame && count++ < 10) DoSleep(1);
13421         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13422     }
13423
13424     /* Kill off chess programs */
13425     if (first.pr != NoProc) {
13426         ExitAnalyzeMode();
13427
13428         DoSleep( appData.delayBeforeQuit );
13429         SendToProgram("quit\n", &first);
13430         DoSleep( appData.delayAfterQuit );
13431         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13432     }
13433     if (second.pr != NoProc) {
13434         DoSleep( appData.delayBeforeQuit );
13435         SendToProgram("quit\n", &second);
13436         DoSleep( appData.delayAfterQuit );
13437         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13438     }
13439     if (first.isr != NULL) {
13440         RemoveInputSource(first.isr);
13441     }
13442     if (second.isr != NULL) {
13443         RemoveInputSource(second.isr);
13444     }
13445
13446     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13447     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13448
13449     ShutDownFrontEnd();
13450     exit(status);
13451 }
13452
13453 void
13454 PauseEngine (ChessProgramState *cps)
13455 {
13456     SendToProgram("pause\n", cps);
13457     cps->pause = 2;
13458 }
13459
13460 void
13461 UnPauseEngine (ChessProgramState *cps)
13462 {
13463     SendToProgram("resume\n", cps);
13464     cps->pause = 1;
13465 }
13466
13467 void
13468 PauseEvent ()
13469 {
13470     if (appData.debugMode)
13471         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13472     if (pausing) {
13473         pausing = FALSE;
13474         ModeHighlight();
13475         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13476             StartClocks();
13477             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13478                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13479                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13480             }
13481             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13482             HandleMachineMove(stashedInputMove, stalledEngine);
13483             stalledEngine = NULL;
13484             return;
13485         }
13486         if (gameMode == MachinePlaysWhite ||
13487             gameMode == TwoMachinesPlay   ||
13488             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13489             if(first.pause)  UnPauseEngine(&first);
13490             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13491             if(second.pause) UnPauseEngine(&second);
13492             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13493             StartClocks();
13494         } else {
13495             DisplayBothClocks();
13496         }
13497         if (gameMode == PlayFromGameFile) {
13498             if (appData.timeDelay >= 0)
13499                 AutoPlayGameLoop();
13500         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13501             Reset(FALSE, TRUE);
13502             SendToICS(ics_prefix);
13503             SendToICS("refresh\n");
13504         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13505             ForwardInner(forwardMostMove);
13506         }
13507         pauseExamInvalid = FALSE;
13508     } else {
13509         switch (gameMode) {
13510           default:
13511             return;
13512           case IcsExamining:
13513             pauseExamForwardMostMove = forwardMostMove;
13514             pauseExamInvalid = FALSE;
13515             /* fall through */
13516           case IcsObserving:
13517           case IcsPlayingWhite:
13518           case IcsPlayingBlack:
13519             pausing = TRUE;
13520             ModeHighlight();
13521             return;
13522           case PlayFromGameFile:
13523             (void) StopLoadGameTimer();
13524             pausing = TRUE;
13525             ModeHighlight();
13526             break;
13527           case BeginningOfGame:
13528             if (appData.icsActive) return;
13529             /* else fall through */
13530           case MachinePlaysWhite:
13531           case MachinePlaysBlack:
13532           case TwoMachinesPlay:
13533             if (forwardMostMove == 0)
13534               return;           /* don't pause if no one has moved */
13535             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13536                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13537                 if(onMove->pause) {           // thinking engine can be paused
13538                     PauseEngine(onMove);      // do it
13539                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13540                         PauseEngine(onMove->other);
13541                     else
13542                         SendToProgram("easy\n", onMove->other);
13543                     StopClocks();
13544                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13545             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13546                 if(first.pause) {
13547                     PauseEngine(&first);
13548                     StopClocks();
13549                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13550             } else { // human on move, pause pondering by either method
13551                 if(first.pause)
13552                     PauseEngine(&first);
13553                 else if(appData.ponderNextMove)
13554                     SendToProgram("easy\n", &first);
13555                 StopClocks();
13556             }
13557             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13558           case AnalyzeMode:
13559             pausing = TRUE;
13560             ModeHighlight();
13561             break;
13562         }
13563     }
13564 }
13565
13566 void
13567 EditCommentEvent ()
13568 {
13569     char title[MSG_SIZ];
13570
13571     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13572       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13573     } else {
13574       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13575                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13576                parseList[currentMove - 1]);
13577     }
13578
13579     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13580 }
13581
13582
13583 void
13584 EditTagsEvent ()
13585 {
13586     char *tags = PGNTags(&gameInfo);
13587     bookUp = FALSE;
13588     EditTagsPopUp(tags, NULL);
13589     free(tags);
13590 }
13591
13592 void
13593 ToggleSecond ()
13594 {
13595   if(second.analyzing) {
13596     SendToProgram("exit\n", &second);
13597     second.analyzing = FALSE;
13598   } else {
13599     if (second.pr == NoProc) StartChessProgram(&second);
13600     InitChessProgram(&second, FALSE);
13601     FeedMovesToProgram(&second, currentMove);
13602
13603     SendToProgram("analyze\n", &second);
13604     second.analyzing = TRUE;
13605   }
13606 }
13607
13608 /* Toggle ShowThinking */
13609 void
13610 ToggleShowThinking()
13611 {
13612   appData.showThinking = !appData.showThinking;
13613   ShowThinkingEvent();
13614 }
13615
13616 int
13617 AnalyzeModeEvent ()
13618 {
13619     char buf[MSG_SIZ];
13620
13621     if (!first.analysisSupport) {
13622       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13623       DisplayError(buf, 0);
13624       return 0;
13625     }
13626     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13627     if (appData.icsActive) {
13628         if (gameMode != IcsObserving) {
13629           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13630             DisplayError(buf, 0);
13631             /* secure check */
13632             if (appData.icsEngineAnalyze) {
13633                 if (appData.debugMode)
13634                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13635                 ExitAnalyzeMode();
13636                 ModeHighlight();
13637             }
13638             return 0;
13639         }
13640         /* if enable, user wants to disable icsEngineAnalyze */
13641         if (appData.icsEngineAnalyze) {
13642                 ExitAnalyzeMode();
13643                 ModeHighlight();
13644                 return 0;
13645         }
13646         appData.icsEngineAnalyze = TRUE;
13647         if (appData.debugMode)
13648             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13649     }
13650
13651     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13652     if (appData.noChessProgram || gameMode == AnalyzeMode)
13653       return 0;
13654
13655     if (gameMode != AnalyzeFile) {
13656         if (!appData.icsEngineAnalyze) {
13657                EditGameEvent();
13658                if (gameMode != EditGame) return 0;
13659         }
13660         if (!appData.showThinking) ToggleShowThinking();
13661         ResurrectChessProgram();
13662         SendToProgram("analyze\n", &first);
13663         first.analyzing = TRUE;
13664         /*first.maybeThinking = TRUE;*/
13665         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13666         EngineOutputPopUp();
13667     }
13668     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13669     pausing = FALSE;
13670     ModeHighlight();
13671     SetGameInfo();
13672
13673     StartAnalysisClock();
13674     GetTimeMark(&lastNodeCountTime);
13675     lastNodeCount = 0;
13676     return 1;
13677 }
13678
13679 void
13680 AnalyzeFileEvent ()
13681 {
13682     if (appData.noChessProgram || gameMode == AnalyzeFile)
13683       return;
13684
13685     if (!first.analysisSupport) {
13686       char buf[MSG_SIZ];
13687       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13688       DisplayError(buf, 0);
13689       return;
13690     }
13691
13692     if (gameMode != AnalyzeMode) {
13693         keepInfo = 1; // mere annotating should not alter PGN tags
13694         EditGameEvent();
13695         keepInfo = 0;
13696         if (gameMode != EditGame) return;
13697         if (!appData.showThinking) ToggleShowThinking();
13698         ResurrectChessProgram();
13699         SendToProgram("analyze\n", &first);
13700         first.analyzing = TRUE;
13701         /*first.maybeThinking = TRUE;*/
13702         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13703         EngineOutputPopUp();
13704     }
13705     gameMode = AnalyzeFile;
13706     pausing = FALSE;
13707     ModeHighlight();
13708
13709     StartAnalysisClock();
13710     GetTimeMark(&lastNodeCountTime);
13711     lastNodeCount = 0;
13712     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13713     AnalysisPeriodicEvent(1);
13714 }
13715
13716 void
13717 MachineWhiteEvent ()
13718 {
13719     char buf[MSG_SIZ];
13720     char *bookHit = NULL;
13721
13722     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13723       return;
13724
13725
13726     if (gameMode == PlayFromGameFile ||
13727         gameMode == TwoMachinesPlay  ||
13728         gameMode == Training         ||
13729         gameMode == AnalyzeMode      ||
13730         gameMode == EndOfGame)
13731         EditGameEvent();
13732
13733     if (gameMode == EditPosition)
13734         EditPositionDone(TRUE);
13735
13736     if (!WhiteOnMove(currentMove)) {
13737         DisplayError(_("It is not White's turn"), 0);
13738         return;
13739     }
13740
13741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13742       ExitAnalyzeMode();
13743
13744     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13745         gameMode == AnalyzeFile)
13746         TruncateGame();
13747
13748     ResurrectChessProgram();    /* in case it isn't running */
13749     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13750         gameMode = MachinePlaysWhite;
13751         ResetClocks();
13752     } else
13753     gameMode = MachinePlaysWhite;
13754     pausing = FALSE;
13755     ModeHighlight();
13756     SetGameInfo();
13757     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13758     DisplayTitle(buf);
13759     if (first.sendName) {
13760       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13761       SendToProgram(buf, &first);
13762     }
13763     if (first.sendTime) {
13764       if (first.useColors) {
13765         SendToProgram("black\n", &first); /*gnu kludge*/
13766       }
13767       SendTimeRemaining(&first, TRUE);
13768     }
13769     if (first.useColors) {
13770       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13771     }
13772     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13773     SetMachineThinkingEnables();
13774     first.maybeThinking = TRUE;
13775     StartClocks();
13776     firstMove = FALSE;
13777
13778     if (appData.autoFlipView && !flipView) {
13779       flipView = !flipView;
13780       DrawPosition(FALSE, NULL);
13781       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13782     }
13783
13784     if(bookHit) { // [HGM] book: simulate book reply
13785         static char bookMove[MSG_SIZ]; // a bit generous?
13786
13787         programStats.nodes = programStats.depth = programStats.time =
13788         programStats.score = programStats.got_only_move = 0;
13789         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13790
13791         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13792         strcat(bookMove, bookHit);
13793         HandleMachineMove(bookMove, &first);
13794     }
13795 }
13796
13797 void
13798 MachineBlackEvent ()
13799 {
13800   char buf[MSG_SIZ];
13801   char *bookHit = NULL;
13802
13803     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13804         return;
13805
13806
13807     if (gameMode == PlayFromGameFile ||
13808         gameMode == TwoMachinesPlay  ||
13809         gameMode == Training         ||
13810         gameMode == AnalyzeMode      ||
13811         gameMode == EndOfGame)
13812         EditGameEvent();
13813
13814     if (gameMode == EditPosition)
13815         EditPositionDone(TRUE);
13816
13817     if (WhiteOnMove(currentMove)) {
13818         DisplayError(_("It is not Black's turn"), 0);
13819         return;
13820     }
13821
13822     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13823       ExitAnalyzeMode();
13824
13825     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13826         gameMode == AnalyzeFile)
13827         TruncateGame();
13828
13829     ResurrectChessProgram();    /* in case it isn't running */
13830     gameMode = MachinePlaysBlack;
13831     pausing = FALSE;
13832     ModeHighlight();
13833     SetGameInfo();
13834     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13835     DisplayTitle(buf);
13836     if (first.sendName) {
13837       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13838       SendToProgram(buf, &first);
13839     }
13840     if (first.sendTime) {
13841       if (first.useColors) {
13842         SendToProgram("white\n", &first); /*gnu kludge*/
13843       }
13844       SendTimeRemaining(&first, FALSE);
13845     }
13846     if (first.useColors) {
13847       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13848     }
13849     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13850     SetMachineThinkingEnables();
13851     first.maybeThinking = TRUE;
13852     StartClocks();
13853
13854     if (appData.autoFlipView && flipView) {
13855       flipView = !flipView;
13856       DrawPosition(FALSE, NULL);
13857       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13858     }
13859     if(bookHit) { // [HGM] book: simulate book reply
13860         static char bookMove[MSG_SIZ]; // a bit generous?
13861
13862         programStats.nodes = programStats.depth = programStats.time =
13863         programStats.score = programStats.got_only_move = 0;
13864         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13865
13866         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13867         strcat(bookMove, bookHit);
13868         HandleMachineMove(bookMove, &first);
13869     }
13870 }
13871
13872
13873 void
13874 DisplayTwoMachinesTitle ()
13875 {
13876     char buf[MSG_SIZ];
13877     if (appData.matchGames > 0) {
13878         if(appData.tourneyFile[0]) {
13879           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13880                    gameInfo.white, _("vs."), gameInfo.black,
13881                    nextGame+1, appData.matchGames+1,
13882                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13883         } else
13884         if (first.twoMachinesColor[0] == 'w') {
13885           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13886                    gameInfo.white, _("vs."),  gameInfo.black,
13887                    first.matchWins, second.matchWins,
13888                    matchGame - 1 - (first.matchWins + second.matchWins));
13889         } else {
13890           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13891                    gameInfo.white, _("vs."), gameInfo.black,
13892                    second.matchWins, first.matchWins,
13893                    matchGame - 1 - (first.matchWins + second.matchWins));
13894         }
13895     } else {
13896       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13897     }
13898     DisplayTitle(buf);
13899 }
13900
13901 void
13902 SettingsMenuIfReady ()
13903 {
13904   if (second.lastPing != second.lastPong) {
13905     DisplayMessage("", _("Waiting for second chess program"));
13906     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13907     return;
13908   }
13909   ThawUI();
13910   DisplayMessage("", "");
13911   SettingsPopUp(&second);
13912 }
13913
13914 int
13915 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13916 {
13917     char buf[MSG_SIZ];
13918     if (cps->pr == NoProc) {
13919         StartChessProgram(cps);
13920         if (cps->protocolVersion == 1) {
13921           retry();
13922         } else {
13923           /* kludge: allow timeout for initial "feature" command */
13924           FreezeUI();
13925           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13926           DisplayMessage("", buf);
13927           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13928         }
13929         return 1;
13930     }
13931     return 0;
13932 }
13933
13934 void
13935 TwoMachinesEvent P((void))
13936 {
13937     int i;
13938     char buf[MSG_SIZ];
13939     ChessProgramState *onmove;
13940     char *bookHit = NULL;
13941     static int stalling = 0;
13942     TimeMark now;
13943     long wait;
13944
13945     if (appData.noChessProgram) return;
13946
13947     switch (gameMode) {
13948       case TwoMachinesPlay:
13949         return;
13950       case MachinePlaysWhite:
13951       case MachinePlaysBlack:
13952         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13953             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13954             return;
13955         }
13956         /* fall through */
13957       case BeginningOfGame:
13958       case PlayFromGameFile:
13959       case EndOfGame:
13960         EditGameEvent();
13961         if (gameMode != EditGame) return;
13962         break;
13963       case EditPosition:
13964         EditPositionDone(TRUE);
13965         break;
13966       case AnalyzeMode:
13967       case AnalyzeFile:
13968         ExitAnalyzeMode();
13969         break;
13970       case EditGame:
13971       default:
13972         break;
13973     }
13974
13975 //    forwardMostMove = currentMove;
13976     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13977
13978     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13979
13980     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13981     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13982       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13983       return;
13984     }
13985
13986     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13987         DisplayError("second engine does not play this", 0);
13988         return;
13989     }
13990
13991     if(!stalling) {
13992       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13993       SendToProgram("force\n", &second);
13994       stalling = 1;
13995       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13996       return;
13997     }
13998     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13999     if(appData.matchPause>10000 || appData.matchPause<10)
14000                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14001     wait = SubtractTimeMarks(&now, &pauseStart);
14002     if(wait < appData.matchPause) {
14003         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14004         return;
14005     }
14006     // we are now committed to starting the game
14007     stalling = 0;
14008     DisplayMessage("", "");
14009     if (startedFromSetupPosition) {
14010         SendBoard(&second, backwardMostMove);
14011     if (appData.debugMode) {
14012         fprintf(debugFP, "Two Machines\n");
14013     }
14014     }
14015     for (i = backwardMostMove; i < forwardMostMove; i++) {
14016         SendMoveToProgram(i, &second);
14017     }
14018
14019     gameMode = TwoMachinesPlay;
14020     pausing = FALSE;
14021     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14022     SetGameInfo();
14023     DisplayTwoMachinesTitle();
14024     firstMove = TRUE;
14025     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14026         onmove = &first;
14027     } else {
14028         onmove = &second;
14029     }
14030     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14031     SendToProgram(first.computerString, &first);
14032     if (first.sendName) {
14033       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14034       SendToProgram(buf, &first);
14035     }
14036     SendToProgram(second.computerString, &second);
14037     if (second.sendName) {
14038       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14039       SendToProgram(buf, &second);
14040     }
14041
14042     ResetClocks();
14043     if (!first.sendTime || !second.sendTime) {
14044         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14045         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14046     }
14047     if (onmove->sendTime) {
14048       if (onmove->useColors) {
14049         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14050       }
14051       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14052     }
14053     if (onmove->useColors) {
14054       SendToProgram(onmove->twoMachinesColor, onmove);
14055     }
14056     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14057 //    SendToProgram("go\n", onmove);
14058     onmove->maybeThinking = TRUE;
14059     SetMachineThinkingEnables();
14060
14061     StartClocks();
14062
14063     if(bookHit) { // [HGM] book: simulate book reply
14064         static char bookMove[MSG_SIZ]; // a bit generous?
14065
14066         programStats.nodes = programStats.depth = programStats.time =
14067         programStats.score = programStats.got_only_move = 0;
14068         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14069
14070         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14071         strcat(bookMove, bookHit);
14072         savedMessage = bookMove; // args for deferred call
14073         savedState = onmove;
14074         ScheduleDelayedEvent(DeferredBookMove, 1);
14075     }
14076 }
14077
14078 void
14079 TrainingEvent ()
14080 {
14081     if (gameMode == Training) {
14082       SetTrainingModeOff();
14083       gameMode = PlayFromGameFile;
14084       DisplayMessage("", _("Training mode off"));
14085     } else {
14086       gameMode = Training;
14087       animateTraining = appData.animate;
14088
14089       /* make sure we are not already at the end of the game */
14090       if (currentMove < forwardMostMove) {
14091         SetTrainingModeOn();
14092         DisplayMessage("", _("Training mode on"));
14093       } else {
14094         gameMode = PlayFromGameFile;
14095         DisplayError(_("Already at end of game"), 0);
14096       }
14097     }
14098     ModeHighlight();
14099 }
14100
14101 void
14102 IcsClientEvent ()
14103 {
14104     if (!appData.icsActive) return;
14105     switch (gameMode) {
14106       case IcsPlayingWhite:
14107       case IcsPlayingBlack:
14108       case IcsObserving:
14109       case IcsIdle:
14110       case BeginningOfGame:
14111       case IcsExamining:
14112         return;
14113
14114       case EditGame:
14115         break;
14116
14117       case EditPosition:
14118         EditPositionDone(TRUE);
14119         break;
14120
14121       case AnalyzeMode:
14122       case AnalyzeFile:
14123         ExitAnalyzeMode();
14124         break;
14125
14126       default:
14127         EditGameEvent();
14128         break;
14129     }
14130
14131     gameMode = IcsIdle;
14132     ModeHighlight();
14133     return;
14134 }
14135
14136 void
14137 EditGameEvent ()
14138 {
14139     int i;
14140
14141     switch (gameMode) {
14142       case Training:
14143         SetTrainingModeOff();
14144         break;
14145       case MachinePlaysWhite:
14146       case MachinePlaysBlack:
14147       case BeginningOfGame:
14148         SendToProgram("force\n", &first);
14149         SetUserThinkingEnables();
14150         break;
14151       case PlayFromGameFile:
14152         (void) StopLoadGameTimer();
14153         if (gameFileFP != NULL) {
14154             gameFileFP = NULL;
14155         }
14156         break;
14157       case EditPosition:
14158         EditPositionDone(TRUE);
14159         break;
14160       case AnalyzeMode:
14161       case AnalyzeFile:
14162         ExitAnalyzeMode();
14163         SendToProgram("force\n", &first);
14164         break;
14165       case TwoMachinesPlay:
14166         GameEnds(EndOfFile, NULL, GE_PLAYER);
14167         ResurrectChessProgram();
14168         SetUserThinkingEnables();
14169         break;
14170       case EndOfGame:
14171         ResurrectChessProgram();
14172         break;
14173       case IcsPlayingBlack:
14174       case IcsPlayingWhite:
14175         DisplayError(_("Warning: You are still playing a game"), 0);
14176         break;
14177       case IcsObserving:
14178         DisplayError(_("Warning: You are still observing a game"), 0);
14179         break;
14180       case IcsExamining:
14181         DisplayError(_("Warning: You are still examining a game"), 0);
14182         break;
14183       case IcsIdle:
14184         break;
14185       case EditGame:
14186       default:
14187         return;
14188     }
14189
14190     pausing = FALSE;
14191     StopClocks();
14192     first.offeredDraw = second.offeredDraw = 0;
14193
14194     if (gameMode == PlayFromGameFile) {
14195         whiteTimeRemaining = timeRemaining[0][currentMove];
14196         blackTimeRemaining = timeRemaining[1][currentMove];
14197         DisplayTitle("");
14198     }
14199
14200     if (gameMode == MachinePlaysWhite ||
14201         gameMode == MachinePlaysBlack ||
14202         gameMode == TwoMachinesPlay ||
14203         gameMode == EndOfGame) {
14204         i = forwardMostMove;
14205         while (i > currentMove) {
14206             SendToProgram("undo\n", &first);
14207             i--;
14208         }
14209         if(!adjustedClock) {
14210         whiteTimeRemaining = timeRemaining[0][currentMove];
14211         blackTimeRemaining = timeRemaining[1][currentMove];
14212         DisplayBothClocks();
14213         }
14214         if (whiteFlag || blackFlag) {
14215             whiteFlag = blackFlag = 0;
14216         }
14217         DisplayTitle("");
14218     }
14219
14220     gameMode = EditGame;
14221     ModeHighlight();
14222     SetGameInfo();
14223 }
14224
14225
14226 void
14227 EditPositionEvent ()
14228 {
14229     if (gameMode == EditPosition) {
14230         EditGameEvent();
14231         return;
14232     }
14233
14234     EditGameEvent();
14235     if (gameMode != EditGame) return;
14236
14237     gameMode = EditPosition;
14238     ModeHighlight();
14239     SetGameInfo();
14240     if (currentMove > 0)
14241       CopyBoard(boards[0], boards[currentMove]);
14242
14243     blackPlaysFirst = !WhiteOnMove(currentMove);
14244     ResetClocks();
14245     currentMove = forwardMostMove = backwardMostMove = 0;
14246     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14247     DisplayMove(-1);
14248     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14249 }
14250
14251 void
14252 ExitAnalyzeMode ()
14253 {
14254     /* [DM] icsEngineAnalyze - possible call from other functions */
14255     if (appData.icsEngineAnalyze) {
14256         appData.icsEngineAnalyze = FALSE;
14257
14258         DisplayMessage("",_("Close ICS engine analyze..."));
14259     }
14260     if (first.analysisSupport && first.analyzing) {
14261       SendToBoth("exit\n");
14262       first.analyzing = second.analyzing = FALSE;
14263     }
14264     thinkOutput[0] = NULLCHAR;
14265 }
14266
14267 void
14268 EditPositionDone (Boolean fakeRights)
14269 {
14270     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14271
14272     startedFromSetupPosition = TRUE;
14273     InitChessProgram(&first, FALSE);
14274     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14275       boards[0][EP_STATUS] = EP_NONE;
14276       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14277       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14278         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14279         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14280       } else boards[0][CASTLING][2] = NoRights;
14281       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14282         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14283         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14284       } else boards[0][CASTLING][5] = NoRights;
14285       if(gameInfo.variant == VariantSChess) {
14286         int i;
14287         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14288           boards[0][VIRGIN][i] = 0;
14289           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14290           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14291         }
14292       }
14293     }
14294     SendToProgram("force\n", &first);
14295     if (blackPlaysFirst) {
14296         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14297         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14298         currentMove = forwardMostMove = backwardMostMove = 1;
14299         CopyBoard(boards[1], boards[0]);
14300     } else {
14301         currentMove = forwardMostMove = backwardMostMove = 0;
14302     }
14303     SendBoard(&first, forwardMostMove);
14304     if (appData.debugMode) {
14305         fprintf(debugFP, "EditPosDone\n");
14306     }
14307     DisplayTitle("");
14308     DisplayMessage("", "");
14309     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14310     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14311     gameMode = EditGame;
14312     ModeHighlight();
14313     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14314     ClearHighlights(); /* [AS] */
14315 }
14316
14317 /* Pause for `ms' milliseconds */
14318 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14319 void
14320 TimeDelay (long ms)
14321 {
14322     TimeMark m1, m2;
14323
14324     GetTimeMark(&m1);
14325     do {
14326         GetTimeMark(&m2);
14327     } while (SubtractTimeMarks(&m2, &m1) < ms);
14328 }
14329
14330 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14331 void
14332 SendMultiLineToICS (char *buf)
14333 {
14334     char temp[MSG_SIZ+1], *p;
14335     int len;
14336
14337     len = strlen(buf);
14338     if (len > MSG_SIZ)
14339       len = MSG_SIZ;
14340
14341     strncpy(temp, buf, len);
14342     temp[len] = 0;
14343
14344     p = temp;
14345     while (*p) {
14346         if (*p == '\n' || *p == '\r')
14347           *p = ' ';
14348         ++p;
14349     }
14350
14351     strcat(temp, "\n");
14352     SendToICS(temp);
14353     SendToPlayer(temp, strlen(temp));
14354 }
14355
14356 void
14357 SetWhiteToPlayEvent ()
14358 {
14359     if (gameMode == EditPosition) {
14360         blackPlaysFirst = FALSE;
14361         DisplayBothClocks();    /* works because currentMove is 0 */
14362     } else if (gameMode == IcsExamining) {
14363         SendToICS(ics_prefix);
14364         SendToICS("tomove white\n");
14365     }
14366 }
14367
14368 void
14369 SetBlackToPlayEvent ()
14370 {
14371     if (gameMode == EditPosition) {
14372         blackPlaysFirst = TRUE;
14373         currentMove = 1;        /* kludge */
14374         DisplayBothClocks();
14375         currentMove = 0;
14376     } else if (gameMode == IcsExamining) {
14377         SendToICS(ics_prefix);
14378         SendToICS("tomove black\n");
14379     }
14380 }
14381
14382 void
14383 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14384 {
14385     char buf[MSG_SIZ];
14386     ChessSquare piece = boards[0][y][x];
14387
14388     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14389
14390     switch (selection) {
14391       case ClearBoard:
14392         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14393             SendToICS(ics_prefix);
14394             SendToICS("bsetup clear\n");
14395         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14396             SendToICS(ics_prefix);
14397             SendToICS("clearboard\n");
14398         } else {
14399             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14400                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14401                 for (y = 0; y < BOARD_HEIGHT; y++) {
14402                     if (gameMode == IcsExamining) {
14403                         if (boards[currentMove][y][x] != EmptySquare) {
14404                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14405                                     AAA + x, ONE + y);
14406                             SendToICS(buf);
14407                         }
14408                     } else {
14409                         boards[0][y][x] = p;
14410                     }
14411                 }
14412             }
14413         }
14414         if (gameMode == EditPosition) {
14415             DrawPosition(FALSE, boards[0]);
14416         }
14417         break;
14418
14419       case WhitePlay:
14420         SetWhiteToPlayEvent();
14421         break;
14422
14423       case BlackPlay:
14424         SetBlackToPlayEvent();
14425         break;
14426
14427       case EmptySquare:
14428         if (gameMode == IcsExamining) {
14429             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14430             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14431             SendToICS(buf);
14432         } else {
14433             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14434                 if(x == BOARD_LEFT-2) {
14435                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14436                     boards[0][y][1] = 0;
14437                 } else
14438                 if(x == BOARD_RGHT+1) {
14439                     if(y >= gameInfo.holdingsSize) break;
14440                     boards[0][y][BOARD_WIDTH-2] = 0;
14441                 } else break;
14442             }
14443             boards[0][y][x] = EmptySquare;
14444             DrawPosition(FALSE, boards[0]);
14445         }
14446         break;
14447
14448       case PromotePiece:
14449         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14450            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14451             selection = (ChessSquare) (PROMOTED piece);
14452         } else if(piece == EmptySquare) selection = WhiteSilver;
14453         else selection = (ChessSquare)((int)piece - 1);
14454         goto defaultlabel;
14455
14456       case DemotePiece:
14457         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14458            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14459             selection = (ChessSquare) (DEMOTED piece);
14460         } else if(piece == EmptySquare) selection = BlackSilver;
14461         else selection = (ChessSquare)((int)piece + 1);
14462         goto defaultlabel;
14463
14464       case WhiteQueen:
14465       case BlackQueen:
14466         if(gameInfo.variant == VariantShatranj ||
14467            gameInfo.variant == VariantXiangqi  ||
14468            gameInfo.variant == VariantCourier  ||
14469            gameInfo.variant == VariantMakruk     )
14470             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14471         goto defaultlabel;
14472
14473       case WhiteKing:
14474       case BlackKing:
14475         if(gameInfo.variant == VariantXiangqi)
14476             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14477         if(gameInfo.variant == VariantKnightmate)
14478             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14479       default:
14480         defaultlabel:
14481         if (gameMode == IcsExamining) {
14482             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14483             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14484                      PieceToChar(selection), AAA + x, ONE + y);
14485             SendToICS(buf);
14486         } else {
14487             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14488                 int n;
14489                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14490                     n = PieceToNumber(selection - BlackPawn);
14491                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14492                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14493                     boards[0][BOARD_HEIGHT-1-n][1]++;
14494                 } else
14495                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14496                     n = PieceToNumber(selection);
14497                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14498                     boards[0][n][BOARD_WIDTH-1] = selection;
14499                     boards[0][n][BOARD_WIDTH-2]++;
14500                 }
14501             } else
14502             boards[0][y][x] = selection;
14503             DrawPosition(TRUE, boards[0]);
14504             ClearHighlights();
14505             fromX = fromY = -1;
14506         }
14507         break;
14508     }
14509 }
14510
14511
14512 void
14513 DropMenuEvent (ChessSquare selection, int x, int y)
14514 {
14515     ChessMove moveType;
14516
14517     switch (gameMode) {
14518       case IcsPlayingWhite:
14519       case MachinePlaysBlack:
14520         if (!WhiteOnMove(currentMove)) {
14521             DisplayMoveError(_("It is Black's turn"));
14522             return;
14523         }
14524         moveType = WhiteDrop;
14525         break;
14526       case IcsPlayingBlack:
14527       case MachinePlaysWhite:
14528         if (WhiteOnMove(currentMove)) {
14529             DisplayMoveError(_("It is White's turn"));
14530             return;
14531         }
14532         moveType = BlackDrop;
14533         break;
14534       case EditGame:
14535         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14536         break;
14537       default:
14538         return;
14539     }
14540
14541     if (moveType == BlackDrop && selection < BlackPawn) {
14542       selection = (ChessSquare) ((int) selection
14543                                  + (int) BlackPawn - (int) WhitePawn);
14544     }
14545     if (boards[currentMove][y][x] != EmptySquare) {
14546         DisplayMoveError(_("That square is occupied"));
14547         return;
14548     }
14549
14550     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14551 }
14552
14553 void
14554 AcceptEvent ()
14555 {
14556     /* Accept a pending offer of any kind from opponent */
14557
14558     if (appData.icsActive) {
14559         SendToICS(ics_prefix);
14560         SendToICS("accept\n");
14561     } else if (cmailMsgLoaded) {
14562         if (currentMove == cmailOldMove &&
14563             commentList[cmailOldMove] != NULL &&
14564             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14565                    "Black offers a draw" : "White offers a draw")) {
14566             TruncateGame();
14567             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14568             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14569         } else {
14570             DisplayError(_("There is no pending offer on this move"), 0);
14571             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14572         }
14573     } else {
14574         /* Not used for offers from chess program */
14575     }
14576 }
14577
14578 void
14579 DeclineEvent ()
14580 {
14581     /* Decline a pending offer of any kind from opponent */
14582
14583     if (appData.icsActive) {
14584         SendToICS(ics_prefix);
14585         SendToICS("decline\n");
14586     } else if (cmailMsgLoaded) {
14587         if (currentMove == cmailOldMove &&
14588             commentList[cmailOldMove] != NULL &&
14589             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14590                    "Black offers a draw" : "White offers a draw")) {
14591 #ifdef NOTDEF
14592             AppendComment(cmailOldMove, "Draw declined", TRUE);
14593             DisplayComment(cmailOldMove - 1, "Draw declined");
14594 #endif /*NOTDEF*/
14595         } else {
14596             DisplayError(_("There is no pending offer on this move"), 0);
14597         }
14598     } else {
14599         /* Not used for offers from chess program */
14600     }
14601 }
14602
14603 void
14604 RematchEvent ()
14605 {
14606     /* Issue ICS rematch command */
14607     if (appData.icsActive) {
14608         SendToICS(ics_prefix);
14609         SendToICS("rematch\n");
14610     }
14611 }
14612
14613 void
14614 CallFlagEvent ()
14615 {
14616     /* Call your opponent's flag (claim a win on time) */
14617     if (appData.icsActive) {
14618         SendToICS(ics_prefix);
14619         SendToICS("flag\n");
14620     } else {
14621         switch (gameMode) {
14622           default:
14623             return;
14624           case MachinePlaysWhite:
14625             if (whiteFlag) {
14626                 if (blackFlag)
14627                   GameEnds(GameIsDrawn, "Both players ran out of time",
14628                            GE_PLAYER);
14629                 else
14630                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14631             } else {
14632                 DisplayError(_("Your opponent is not out of time"), 0);
14633             }
14634             break;
14635           case MachinePlaysBlack:
14636             if (blackFlag) {
14637                 if (whiteFlag)
14638                   GameEnds(GameIsDrawn, "Both players ran out of time",
14639                            GE_PLAYER);
14640                 else
14641                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14642             } else {
14643                 DisplayError(_("Your opponent is not out of time"), 0);
14644             }
14645             break;
14646         }
14647     }
14648 }
14649
14650 void
14651 ClockClick (int which)
14652 {       // [HGM] code moved to back-end from winboard.c
14653         if(which) { // black clock
14654           if (gameMode == EditPosition || gameMode == IcsExamining) {
14655             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14656             SetBlackToPlayEvent();
14657           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14658           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14659           } else if (shiftKey) {
14660             AdjustClock(which, -1);
14661           } else if (gameMode == IcsPlayingWhite ||
14662                      gameMode == MachinePlaysBlack) {
14663             CallFlagEvent();
14664           }
14665         } else { // white clock
14666           if (gameMode == EditPosition || gameMode == IcsExamining) {
14667             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14668             SetWhiteToPlayEvent();
14669           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14670           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14671           } else if (shiftKey) {
14672             AdjustClock(which, -1);
14673           } else if (gameMode == IcsPlayingBlack ||
14674                    gameMode == MachinePlaysWhite) {
14675             CallFlagEvent();
14676           }
14677         }
14678 }
14679
14680 void
14681 DrawEvent ()
14682 {
14683     /* Offer draw or accept pending draw offer from opponent */
14684
14685     if (appData.icsActive) {
14686         /* Note: tournament rules require draw offers to be
14687            made after you make your move but before you punch
14688            your clock.  Currently ICS doesn't let you do that;
14689            instead, you immediately punch your clock after making
14690            a move, but you can offer a draw at any time. */
14691
14692         SendToICS(ics_prefix);
14693         SendToICS("draw\n");
14694         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14695     } else if (cmailMsgLoaded) {
14696         if (currentMove == cmailOldMove &&
14697             commentList[cmailOldMove] != NULL &&
14698             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14699                    "Black offers a draw" : "White offers a draw")) {
14700             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14701             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14702         } else if (currentMove == cmailOldMove + 1) {
14703             char *offer = WhiteOnMove(cmailOldMove) ?
14704               "White offers a draw" : "Black offers a draw";
14705             AppendComment(currentMove, offer, TRUE);
14706             DisplayComment(currentMove - 1, offer);
14707             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14708         } else {
14709             DisplayError(_("You must make your move before offering a draw"), 0);
14710             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14711         }
14712     } else if (first.offeredDraw) {
14713         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14714     } else {
14715         if (first.sendDrawOffers) {
14716             SendToProgram("draw\n", &first);
14717             userOfferedDraw = TRUE;
14718         }
14719     }
14720 }
14721
14722 void
14723 AdjournEvent ()
14724 {
14725     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14726
14727     if (appData.icsActive) {
14728         SendToICS(ics_prefix);
14729         SendToICS("adjourn\n");
14730     } else {
14731         /* Currently GNU Chess doesn't offer or accept Adjourns */
14732     }
14733 }
14734
14735
14736 void
14737 AbortEvent ()
14738 {
14739     /* Offer Abort or accept pending Abort offer from opponent */
14740
14741     if (appData.icsActive) {
14742         SendToICS(ics_prefix);
14743         SendToICS("abort\n");
14744     } else {
14745         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14746     }
14747 }
14748
14749 void
14750 ResignEvent ()
14751 {
14752     /* Resign.  You can do this even if it's not your turn. */
14753
14754     if (appData.icsActive) {
14755         SendToICS(ics_prefix);
14756         SendToICS("resign\n");
14757     } else {
14758         switch (gameMode) {
14759           case MachinePlaysWhite:
14760             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14761             break;
14762           case MachinePlaysBlack:
14763             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14764             break;
14765           case EditGame:
14766             if (cmailMsgLoaded) {
14767                 TruncateGame();
14768                 if (WhiteOnMove(cmailOldMove)) {
14769                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14770                 } else {
14771                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14772                 }
14773                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14774             }
14775             break;
14776           default:
14777             break;
14778         }
14779     }
14780 }
14781
14782
14783 void
14784 StopObservingEvent ()
14785 {
14786     /* Stop observing current games */
14787     SendToICS(ics_prefix);
14788     SendToICS("unobserve\n");
14789 }
14790
14791 void
14792 StopExaminingEvent ()
14793 {
14794     /* Stop observing current game */
14795     SendToICS(ics_prefix);
14796     SendToICS("unexamine\n");
14797 }
14798
14799 void
14800 ForwardInner (int target)
14801 {
14802     int limit; int oldSeekGraphUp = seekGraphUp;
14803
14804     if (appData.debugMode)
14805         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14806                 target, currentMove, forwardMostMove);
14807
14808     if (gameMode == EditPosition)
14809       return;
14810
14811     seekGraphUp = FALSE;
14812     MarkTargetSquares(1);
14813
14814     if (gameMode == PlayFromGameFile && !pausing)
14815       PauseEvent();
14816
14817     if (gameMode == IcsExamining && pausing)
14818       limit = pauseExamForwardMostMove;
14819     else
14820       limit = forwardMostMove;
14821
14822     if (target > limit) target = limit;
14823
14824     if (target > 0 && moveList[target - 1][0]) {
14825         int fromX, fromY, toX, toY;
14826         toX = moveList[target - 1][2] - AAA;
14827         toY = moveList[target - 1][3] - ONE;
14828         if (moveList[target - 1][1] == '@') {
14829             if (appData.highlightLastMove) {
14830                 SetHighlights(-1, -1, toX, toY);
14831             }
14832         } else {
14833             fromX = moveList[target - 1][0] - AAA;
14834             fromY = moveList[target - 1][1] - ONE;
14835             if (target == currentMove + 1) {
14836                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14837             }
14838             if (appData.highlightLastMove) {
14839                 SetHighlights(fromX, fromY, toX, toY);
14840             }
14841         }
14842     }
14843     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14844         gameMode == Training || gameMode == PlayFromGameFile ||
14845         gameMode == AnalyzeFile) {
14846         while (currentMove < target) {
14847             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14848             SendMoveToProgram(currentMove++, &first);
14849         }
14850     } else {
14851         currentMove = target;
14852     }
14853
14854     if (gameMode == EditGame || gameMode == EndOfGame) {
14855         whiteTimeRemaining = timeRemaining[0][currentMove];
14856         blackTimeRemaining = timeRemaining[1][currentMove];
14857     }
14858     DisplayBothClocks();
14859     DisplayMove(currentMove - 1);
14860     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14861     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14862     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14863         DisplayComment(currentMove - 1, commentList[currentMove]);
14864     }
14865     ClearMap(); // [HGM] exclude: invalidate map
14866 }
14867
14868
14869 void
14870 ForwardEvent ()
14871 {
14872     if (gameMode == IcsExamining && !pausing) {
14873         SendToICS(ics_prefix);
14874         SendToICS("forward\n");
14875     } else {
14876         ForwardInner(currentMove + 1);
14877     }
14878 }
14879
14880 void
14881 ToEndEvent ()
14882 {
14883     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14884         /* to optimze, we temporarily turn off analysis mode while we feed
14885          * the remaining moves to the engine. Otherwise we get analysis output
14886          * after each move.
14887          */
14888         if (first.analysisSupport) {
14889           SendToProgram("exit\nforce\n", &first);
14890           first.analyzing = FALSE;
14891         }
14892     }
14893
14894     if (gameMode == IcsExamining && !pausing) {
14895         SendToICS(ics_prefix);
14896         SendToICS("forward 999999\n");
14897     } else {
14898         ForwardInner(forwardMostMove);
14899     }
14900
14901     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14902         /* we have fed all the moves, so reactivate analysis mode */
14903         SendToProgram("analyze\n", &first);
14904         first.analyzing = TRUE;
14905         /*first.maybeThinking = TRUE;*/
14906         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14907     }
14908 }
14909
14910 void
14911 BackwardInner (int target)
14912 {
14913     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14914
14915     if (appData.debugMode)
14916         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14917                 target, currentMove, forwardMostMove);
14918
14919     if (gameMode == EditPosition) return;
14920     seekGraphUp = FALSE;
14921     MarkTargetSquares(1);
14922     if (currentMove <= backwardMostMove) {
14923         ClearHighlights();
14924         DrawPosition(full_redraw, boards[currentMove]);
14925         return;
14926     }
14927     if (gameMode == PlayFromGameFile && !pausing)
14928       PauseEvent();
14929
14930     if (moveList[target][0]) {
14931         int fromX, fromY, toX, toY;
14932         toX = moveList[target][2] - AAA;
14933         toY = moveList[target][3] - ONE;
14934         if (moveList[target][1] == '@') {
14935             if (appData.highlightLastMove) {
14936                 SetHighlights(-1, -1, toX, toY);
14937             }
14938         } else {
14939             fromX = moveList[target][0] - AAA;
14940             fromY = moveList[target][1] - ONE;
14941             if (target == currentMove - 1) {
14942                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14943             }
14944             if (appData.highlightLastMove) {
14945                 SetHighlights(fromX, fromY, toX, toY);
14946             }
14947         }
14948     }
14949     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14950         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14951         while (currentMove > target) {
14952             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14953                 // null move cannot be undone. Reload program with move history before it.
14954                 int i;
14955                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14956                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14957                 }
14958                 SendBoard(&first, i);
14959               if(second.analyzing) SendBoard(&second, i);
14960                 for(currentMove=i; currentMove<target; currentMove++) {
14961                     SendMoveToProgram(currentMove, &first);
14962                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14963                 }
14964                 break;
14965             }
14966             SendToBoth("undo\n");
14967             currentMove--;
14968         }
14969     } else {
14970         currentMove = target;
14971     }
14972
14973     if (gameMode == EditGame || gameMode == EndOfGame) {
14974         whiteTimeRemaining = timeRemaining[0][currentMove];
14975         blackTimeRemaining = timeRemaining[1][currentMove];
14976     }
14977     DisplayBothClocks();
14978     DisplayMove(currentMove - 1);
14979     DrawPosition(full_redraw, boards[currentMove]);
14980     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14981     // [HGM] PV info: routine tests if comment empty
14982     DisplayComment(currentMove - 1, commentList[currentMove]);
14983     ClearMap(); // [HGM] exclude: invalidate map
14984 }
14985
14986 void
14987 BackwardEvent ()
14988 {
14989     if (gameMode == IcsExamining && !pausing) {
14990         SendToICS(ics_prefix);
14991         SendToICS("backward\n");
14992     } else {
14993         BackwardInner(currentMove - 1);
14994     }
14995 }
14996
14997 void
14998 ToStartEvent ()
14999 {
15000     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15001         /* to optimize, we temporarily turn off analysis mode while we undo
15002          * all the moves. Otherwise we get analysis output after each undo.
15003          */
15004         if (first.analysisSupport) {
15005           SendToProgram("exit\nforce\n", &first);
15006           first.analyzing = FALSE;
15007         }
15008     }
15009
15010     if (gameMode == IcsExamining && !pausing) {
15011         SendToICS(ics_prefix);
15012         SendToICS("backward 999999\n");
15013     } else {
15014         BackwardInner(backwardMostMove);
15015     }
15016
15017     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15018         /* we have fed all the moves, so reactivate analysis mode */
15019         SendToProgram("analyze\n", &first);
15020         first.analyzing = TRUE;
15021         /*first.maybeThinking = TRUE;*/
15022         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15023     }
15024 }
15025
15026 void
15027 ToNrEvent (int to)
15028 {
15029   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15030   if (to >= forwardMostMove) to = forwardMostMove;
15031   if (to <= backwardMostMove) to = backwardMostMove;
15032   if (to < currentMove) {
15033     BackwardInner(to);
15034   } else {
15035     ForwardInner(to);
15036   }
15037 }
15038
15039 void
15040 RevertEvent (Boolean annotate)
15041 {
15042     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15043         return;
15044     }
15045     if (gameMode != IcsExamining) {
15046         DisplayError(_("You are not examining a game"), 0);
15047         return;
15048     }
15049     if (pausing) {
15050         DisplayError(_("You can't revert while pausing"), 0);
15051         return;
15052     }
15053     SendToICS(ics_prefix);
15054     SendToICS("revert\n");
15055 }
15056
15057 void
15058 RetractMoveEvent ()
15059 {
15060     switch (gameMode) {
15061       case MachinePlaysWhite:
15062       case MachinePlaysBlack:
15063         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15064             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15065             return;
15066         }
15067         if (forwardMostMove < 2) return;
15068         currentMove = forwardMostMove = forwardMostMove - 2;
15069         whiteTimeRemaining = timeRemaining[0][currentMove];
15070         blackTimeRemaining = timeRemaining[1][currentMove];
15071         DisplayBothClocks();
15072         DisplayMove(currentMove - 1);
15073         ClearHighlights();/*!! could figure this out*/
15074         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15075         SendToProgram("remove\n", &first);
15076         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15077         break;
15078
15079       case BeginningOfGame:
15080       default:
15081         break;
15082
15083       case IcsPlayingWhite:
15084       case IcsPlayingBlack:
15085         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15086             SendToICS(ics_prefix);
15087             SendToICS("takeback 2\n");
15088         } else {
15089             SendToICS(ics_prefix);
15090             SendToICS("takeback 1\n");
15091         }
15092         break;
15093     }
15094 }
15095
15096 void
15097 MoveNowEvent ()
15098 {
15099     ChessProgramState *cps;
15100
15101     switch (gameMode) {
15102       case MachinePlaysWhite:
15103         if (!WhiteOnMove(forwardMostMove)) {
15104             DisplayError(_("It is your turn"), 0);
15105             return;
15106         }
15107         cps = &first;
15108         break;
15109       case MachinePlaysBlack:
15110         if (WhiteOnMove(forwardMostMove)) {
15111             DisplayError(_("It is your turn"), 0);
15112             return;
15113         }
15114         cps = &first;
15115         break;
15116       case TwoMachinesPlay:
15117         if (WhiteOnMove(forwardMostMove) ==
15118             (first.twoMachinesColor[0] == 'w')) {
15119             cps = &first;
15120         } else {
15121             cps = &second;
15122         }
15123         break;
15124       case BeginningOfGame:
15125       default:
15126         return;
15127     }
15128     SendToProgram("?\n", cps);
15129 }
15130
15131 void
15132 TruncateGameEvent ()
15133 {
15134     EditGameEvent();
15135     if (gameMode != EditGame) return;
15136     TruncateGame();
15137 }
15138
15139 void
15140 TruncateGame ()
15141 {
15142     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15143     if (forwardMostMove > currentMove) {
15144         if (gameInfo.resultDetails != NULL) {
15145             free(gameInfo.resultDetails);
15146             gameInfo.resultDetails = NULL;
15147             gameInfo.result = GameUnfinished;
15148         }
15149         forwardMostMove = currentMove;
15150         HistorySet(parseList, backwardMostMove, forwardMostMove,
15151                    currentMove-1);
15152     }
15153 }
15154
15155 void
15156 HintEvent ()
15157 {
15158     if (appData.noChessProgram) return;
15159     switch (gameMode) {
15160       case MachinePlaysWhite:
15161         if (WhiteOnMove(forwardMostMove)) {
15162             DisplayError(_("Wait until your turn"), 0);
15163             return;
15164         }
15165         break;
15166       case BeginningOfGame:
15167       case MachinePlaysBlack:
15168         if (!WhiteOnMove(forwardMostMove)) {
15169             DisplayError(_("Wait until your turn"), 0);
15170             return;
15171         }
15172         break;
15173       default:
15174         DisplayError(_("No hint available"), 0);
15175         return;
15176     }
15177     SendToProgram("hint\n", &first);
15178     hintRequested = TRUE;
15179 }
15180
15181 void
15182 CreateBookEvent ()
15183 {
15184     ListGame * lg = (ListGame *) gameList.head;
15185     FILE *f;
15186     int nItem;
15187     static int secondTime = FALSE;
15188
15189     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15190         DisplayError(_("Game list not loaded or empty"), 0);
15191         return;
15192     }
15193
15194     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15195         fclose(f);
15196         secondTime++;
15197         DisplayNote(_("Book file exists! Try again for overwrite."));
15198         return;
15199     }
15200
15201     creatingBook = TRUE;
15202     secondTime = FALSE;
15203
15204     /* Get list size */
15205     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15206         LoadGame(f, nItem, "", TRUE);
15207         AddGameToBook(TRUE);
15208         lg = (ListGame *) lg->node.succ;
15209     }
15210
15211     creatingBook = FALSE;
15212     FlushBook();
15213 }
15214
15215 void
15216 BookEvent ()
15217 {
15218     if (appData.noChessProgram) return;
15219     switch (gameMode) {
15220       case MachinePlaysWhite:
15221         if (WhiteOnMove(forwardMostMove)) {
15222             DisplayError(_("Wait until your turn"), 0);
15223             return;
15224         }
15225         break;
15226       case BeginningOfGame:
15227       case MachinePlaysBlack:
15228         if (!WhiteOnMove(forwardMostMove)) {
15229             DisplayError(_("Wait until your turn"), 0);
15230             return;
15231         }
15232         break;
15233       case EditPosition:
15234         EditPositionDone(TRUE);
15235         break;
15236       case TwoMachinesPlay:
15237         return;
15238       default:
15239         break;
15240     }
15241     SendToProgram("bk\n", &first);
15242     bookOutput[0] = NULLCHAR;
15243     bookRequested = TRUE;
15244 }
15245
15246 void
15247 AboutGameEvent ()
15248 {
15249     char *tags = PGNTags(&gameInfo);
15250     TagsPopUp(tags, CmailMsg());
15251     free(tags);
15252 }
15253
15254 /* end button procedures */
15255
15256 void
15257 PrintPosition (FILE *fp, int move)
15258 {
15259     int i, j;
15260
15261     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15262         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15263             char c = PieceToChar(boards[move][i][j]);
15264             fputc(c == 'x' ? '.' : c, fp);
15265             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15266         }
15267     }
15268     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15269       fprintf(fp, "white to play\n");
15270     else
15271       fprintf(fp, "black to play\n");
15272 }
15273
15274 void
15275 PrintOpponents (FILE *fp)
15276 {
15277     if (gameInfo.white != NULL) {
15278         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15279     } else {
15280         fprintf(fp, "\n");
15281     }
15282 }
15283
15284 /* Find last component of program's own name, using some heuristics */
15285 void
15286 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15287 {
15288     char *p, *q, c;
15289     int local = (strcmp(host, "localhost") == 0);
15290     while (!local && (p = strchr(prog, ';')) != NULL) {
15291         p++;
15292         while (*p == ' ') p++;
15293         prog = p;
15294     }
15295     if (*prog == '"' || *prog == '\'') {
15296         q = strchr(prog + 1, *prog);
15297     } else {
15298         q = strchr(prog, ' ');
15299     }
15300     if (q == NULL) q = prog + strlen(prog);
15301     p = q;
15302     while (p >= prog && *p != '/' && *p != '\\') p--;
15303     p++;
15304     if(p == prog && *p == '"') p++;
15305     c = *q; *q = 0;
15306     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15307     memcpy(buf, p, q - p);
15308     buf[q - p] = NULLCHAR;
15309     if (!local) {
15310         strcat(buf, "@");
15311         strcat(buf, host);
15312     }
15313 }
15314
15315 char *
15316 TimeControlTagValue ()
15317 {
15318     char buf[MSG_SIZ];
15319     if (!appData.clockMode) {
15320       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15321     } else if (movesPerSession > 0) {
15322       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15323     } else if (timeIncrement == 0) {
15324       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15325     } else {
15326       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15327     }
15328     return StrSave(buf);
15329 }
15330
15331 void
15332 SetGameInfo ()
15333 {
15334     /* This routine is used only for certain modes */
15335     VariantClass v = gameInfo.variant;
15336     ChessMove r = GameUnfinished;
15337     char *p = NULL;
15338
15339     if(keepInfo) return;
15340
15341     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15342         r = gameInfo.result;
15343         p = gameInfo.resultDetails;
15344         gameInfo.resultDetails = NULL;
15345     }
15346     ClearGameInfo(&gameInfo);
15347     gameInfo.variant = v;
15348
15349     switch (gameMode) {
15350       case MachinePlaysWhite:
15351         gameInfo.event = StrSave( appData.pgnEventHeader );
15352         gameInfo.site = StrSave(HostName());
15353         gameInfo.date = PGNDate();
15354         gameInfo.round = StrSave("-");
15355         gameInfo.white = StrSave(first.tidy);
15356         gameInfo.black = StrSave(UserName());
15357         gameInfo.timeControl = TimeControlTagValue();
15358         break;
15359
15360       case MachinePlaysBlack:
15361         gameInfo.event = StrSave( appData.pgnEventHeader );
15362         gameInfo.site = StrSave(HostName());
15363         gameInfo.date = PGNDate();
15364         gameInfo.round = StrSave("-");
15365         gameInfo.white = StrSave(UserName());
15366         gameInfo.black = StrSave(first.tidy);
15367         gameInfo.timeControl = TimeControlTagValue();
15368         break;
15369
15370       case TwoMachinesPlay:
15371         gameInfo.event = StrSave( appData.pgnEventHeader );
15372         gameInfo.site = StrSave(HostName());
15373         gameInfo.date = PGNDate();
15374         if (roundNr > 0) {
15375             char buf[MSG_SIZ];
15376             snprintf(buf, MSG_SIZ, "%d", roundNr);
15377             gameInfo.round = StrSave(buf);
15378         } else {
15379             gameInfo.round = StrSave("-");
15380         }
15381         if (first.twoMachinesColor[0] == 'w') {
15382             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15383             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15384         } else {
15385             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15386             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15387         }
15388         gameInfo.timeControl = TimeControlTagValue();
15389         break;
15390
15391       case EditGame:
15392         gameInfo.event = StrSave("Edited game");
15393         gameInfo.site = StrSave(HostName());
15394         gameInfo.date = PGNDate();
15395         gameInfo.round = StrSave("-");
15396         gameInfo.white = StrSave("-");
15397         gameInfo.black = StrSave("-");
15398         gameInfo.result = r;
15399         gameInfo.resultDetails = p;
15400         break;
15401
15402       case EditPosition:
15403         gameInfo.event = StrSave("Edited position");
15404         gameInfo.site = StrSave(HostName());
15405         gameInfo.date = PGNDate();
15406         gameInfo.round = StrSave("-");
15407         gameInfo.white = StrSave("-");
15408         gameInfo.black = StrSave("-");
15409         break;
15410
15411       case IcsPlayingWhite:
15412       case IcsPlayingBlack:
15413       case IcsObserving:
15414       case IcsExamining:
15415         break;
15416
15417       case PlayFromGameFile:
15418         gameInfo.event = StrSave("Game from non-PGN file");
15419         gameInfo.site = StrSave(HostName());
15420         gameInfo.date = PGNDate();
15421         gameInfo.round = StrSave("-");
15422         gameInfo.white = StrSave("?");
15423         gameInfo.black = StrSave("?");
15424         break;
15425
15426       default:
15427         break;
15428     }
15429 }
15430
15431 void
15432 ReplaceComment (int index, char *text)
15433 {
15434     int len;
15435     char *p;
15436     float score;
15437
15438     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15439        pvInfoList[index-1].depth == len &&
15440        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15441        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15442     while (*text == '\n') text++;
15443     len = strlen(text);
15444     while (len > 0 && text[len - 1] == '\n') len--;
15445
15446     if (commentList[index] != NULL)
15447       free(commentList[index]);
15448
15449     if (len == 0) {
15450         commentList[index] = NULL;
15451         return;
15452     }
15453   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15454       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15455       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15456     commentList[index] = (char *) malloc(len + 2);
15457     strncpy(commentList[index], text, len);
15458     commentList[index][len] = '\n';
15459     commentList[index][len + 1] = NULLCHAR;
15460   } else {
15461     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15462     char *p;
15463     commentList[index] = (char *) malloc(len + 7);
15464     safeStrCpy(commentList[index], "{\n", 3);
15465     safeStrCpy(commentList[index]+2, text, len+1);
15466     commentList[index][len+2] = NULLCHAR;
15467     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15468     strcat(commentList[index], "\n}\n");
15469   }
15470 }
15471
15472 void
15473 CrushCRs (char *text)
15474 {
15475   char *p = text;
15476   char *q = text;
15477   char ch;
15478
15479   do {
15480     ch = *p++;
15481     if (ch == '\r') continue;
15482     *q++ = ch;
15483   } while (ch != '\0');
15484 }
15485
15486 void
15487 AppendComment (int index, char *text, Boolean addBraces)
15488 /* addBraces  tells if we should add {} */
15489 {
15490     int oldlen, len;
15491     char *old;
15492
15493 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15494     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15495
15496     CrushCRs(text);
15497     while (*text == '\n') text++;
15498     len = strlen(text);
15499     while (len > 0 && text[len - 1] == '\n') len--;
15500     text[len] = NULLCHAR;
15501
15502     if (len == 0) return;
15503
15504     if (commentList[index] != NULL) {
15505       Boolean addClosingBrace = addBraces;
15506         old = commentList[index];
15507         oldlen = strlen(old);
15508         while(commentList[index][oldlen-1] ==  '\n')
15509           commentList[index][--oldlen] = NULLCHAR;
15510         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15511         safeStrCpy(commentList[index], old, oldlen + len + 6);
15512         free(old);
15513         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15514         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15515           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15516           while (*text == '\n') { text++; len--; }
15517           commentList[index][--oldlen] = NULLCHAR;
15518       }
15519         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15520         else          strcat(commentList[index], "\n");
15521         strcat(commentList[index], text);
15522         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15523         else          strcat(commentList[index], "\n");
15524     } else {
15525         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15526         if(addBraces)
15527           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15528         else commentList[index][0] = NULLCHAR;
15529         strcat(commentList[index], text);
15530         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15531         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15532     }
15533 }
15534
15535 static char *
15536 FindStr (char * text, char * sub_text)
15537 {
15538     char * result = strstr( text, sub_text );
15539
15540     if( result != NULL ) {
15541         result += strlen( sub_text );
15542     }
15543
15544     return result;
15545 }
15546
15547 /* [AS] Try to extract PV info from PGN comment */
15548 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15549 char *
15550 GetInfoFromComment (int index, char * text)
15551 {
15552     char * sep = text, *p;
15553
15554     if( text != NULL && index > 0 ) {
15555         int score = 0;
15556         int depth = 0;
15557         int time = -1, sec = 0, deci;
15558         char * s_eval = FindStr( text, "[%eval " );
15559         char * s_emt = FindStr( text, "[%emt " );
15560
15561         if( s_eval != NULL || s_emt != NULL ) {
15562             /* New style */
15563             char delim;
15564
15565             if( s_eval != NULL ) {
15566                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15567                     return text;
15568                 }
15569
15570                 if( delim != ']' ) {
15571                     return text;
15572                 }
15573             }
15574
15575             if( s_emt != NULL ) {
15576             }
15577                 return text;
15578         }
15579         else {
15580             /* We expect something like: [+|-]nnn.nn/dd */
15581             int score_lo = 0;
15582
15583             if(*text != '{') return text; // [HGM] braces: must be normal comment
15584
15585             sep = strchr( text, '/' );
15586             if( sep == NULL || sep < (text+4) ) {
15587                 return text;
15588             }
15589
15590             p = text;
15591             if(p[1] == '(') { // comment starts with PV
15592                p = strchr(p, ')'); // locate end of PV
15593                if(p == NULL || sep < p+5) return text;
15594                // at this point we have something like "{(.*) +0.23/6 ..."
15595                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15596                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15597                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15598             }
15599             time = -1; sec = -1; deci = -1;
15600             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15601                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15602                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15603                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15604                 return text;
15605             }
15606
15607             if( score_lo < 0 || score_lo >= 100 ) {
15608                 return text;
15609             }
15610
15611             if(sec >= 0) time = 600*time + 10*sec; else
15612             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15613
15614             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15615
15616             /* [HGM] PV time: now locate end of PV info */
15617             while( *++sep >= '0' && *sep <= '9'); // strip depth
15618             if(time >= 0)
15619             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15620             if(sec >= 0)
15621             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15622             if(deci >= 0)
15623             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15624             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15625         }
15626
15627         if( depth <= 0 ) {
15628             return text;
15629         }
15630
15631         if( time < 0 ) {
15632             time = -1;
15633         }
15634
15635         pvInfoList[index-1].depth = depth;
15636         pvInfoList[index-1].score = score;
15637         pvInfoList[index-1].time  = 10*time; // centi-sec
15638         if(*sep == '}') *sep = 0; else *--sep = '{';
15639         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15640     }
15641     return sep;
15642 }
15643
15644 void
15645 SendToProgram (char *message, ChessProgramState *cps)
15646 {
15647     int count, outCount, error;
15648     char buf[MSG_SIZ];
15649
15650     if (cps->pr == NoProc) return;
15651     Attention(cps);
15652
15653     if (appData.debugMode) {
15654         TimeMark now;
15655         GetTimeMark(&now);
15656         fprintf(debugFP, "%ld >%-6s: %s",
15657                 SubtractTimeMarks(&now, &programStartTime),
15658                 cps->which, message);
15659         if(serverFP)
15660             fprintf(serverFP, "%ld >%-6s: %s",
15661                 SubtractTimeMarks(&now, &programStartTime),
15662                 cps->which, message), fflush(serverFP);
15663     }
15664
15665     count = strlen(message);
15666     outCount = OutputToProcess(cps->pr, message, count, &error);
15667     if (outCount < count && !exiting
15668                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15669       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15670       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15671         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15672             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15673                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15674                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15675                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15676             } else {
15677                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15678                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15679                 gameInfo.result = res;
15680             }
15681             gameInfo.resultDetails = StrSave(buf);
15682         }
15683         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15684         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15685     }
15686 }
15687
15688 void
15689 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15690 {
15691     char *end_str;
15692     char buf[MSG_SIZ];
15693     ChessProgramState *cps = (ChessProgramState *)closure;
15694
15695     if (isr != cps->isr) return; /* Killed intentionally */
15696     if (count <= 0) {
15697         if (count == 0) {
15698             RemoveInputSource(cps->isr);
15699             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15700                     _(cps->which), cps->program);
15701             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15702             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15703                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15704                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15705                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15706                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15707                 } else {
15708                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15709                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15710                     gameInfo.result = res;
15711                 }
15712                 gameInfo.resultDetails = StrSave(buf);
15713             }
15714             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15715             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15716         } else {
15717             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15718                     _(cps->which), cps->program);
15719             RemoveInputSource(cps->isr);
15720
15721             /* [AS] Program is misbehaving badly... kill it */
15722             if( count == -2 ) {
15723                 DestroyChildProcess( cps->pr, 9 );
15724                 cps->pr = NoProc;
15725             }
15726
15727             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15728         }
15729         return;
15730     }
15731
15732     if ((end_str = strchr(message, '\r')) != NULL)
15733       *end_str = NULLCHAR;
15734     if ((end_str = strchr(message, '\n')) != NULL)
15735       *end_str = NULLCHAR;
15736
15737     if (appData.debugMode) {
15738         TimeMark now; int print = 1;
15739         char *quote = ""; char c; int i;
15740
15741         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15742                 char start = message[0];
15743                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15744                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15745                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15746                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15747                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15748                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15749                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15750                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15751                    sscanf(message, "hint: %c", &c)!=1 &&
15752                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15753                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15754                     print = (appData.engineComments >= 2);
15755                 }
15756                 message[0] = start; // restore original message
15757         }
15758         if(print) {
15759                 GetTimeMark(&now);
15760                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15761                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15762                         quote,
15763                         message);
15764                 if(serverFP)
15765                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15766                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15767                         quote,
15768                         message), fflush(serverFP);
15769         }
15770     }
15771
15772     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15773     if (appData.icsEngineAnalyze) {
15774         if (strstr(message, "whisper") != NULL ||
15775              strstr(message, "kibitz") != NULL ||
15776             strstr(message, "tellics") != NULL) return;
15777     }
15778
15779     HandleMachineMove(message, cps);
15780 }
15781
15782
15783 void
15784 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15785 {
15786     char buf[MSG_SIZ];
15787     int seconds;
15788
15789     if( timeControl_2 > 0 ) {
15790         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15791             tc = timeControl_2;
15792         }
15793     }
15794     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15795     inc /= cps->timeOdds;
15796     st  /= cps->timeOdds;
15797
15798     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15799
15800     if (st > 0) {
15801       /* Set exact time per move, normally using st command */
15802       if (cps->stKludge) {
15803         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15804         seconds = st % 60;
15805         if (seconds == 0) {
15806           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15807         } else {
15808           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15809         }
15810       } else {
15811         snprintf(buf, MSG_SIZ, "st %d\n", st);
15812       }
15813     } else {
15814       /* Set conventional or incremental time control, using level command */
15815       if (seconds == 0) {
15816         /* Note old gnuchess bug -- minutes:seconds used to not work.
15817            Fixed in later versions, but still avoid :seconds
15818            when seconds is 0. */
15819         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15820       } else {
15821         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15822                  seconds, inc/1000.);
15823       }
15824     }
15825     SendToProgram(buf, cps);
15826
15827     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15828     /* Orthogonally, limit search to given depth */
15829     if (sd > 0) {
15830       if (cps->sdKludge) {
15831         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15832       } else {
15833         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15834       }
15835       SendToProgram(buf, cps);
15836     }
15837
15838     if(cps->nps >= 0) { /* [HGM] nps */
15839         if(cps->supportsNPS == FALSE)
15840           cps->nps = -1; // don't use if engine explicitly says not supported!
15841         else {
15842           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15843           SendToProgram(buf, cps);
15844         }
15845     }
15846 }
15847
15848 ChessProgramState *
15849 WhitePlayer ()
15850 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15851 {
15852     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15853        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15854         return &second;
15855     return &first;
15856 }
15857
15858 void
15859 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15860 {
15861     char message[MSG_SIZ];
15862     long time, otime;
15863
15864     /* Note: this routine must be called when the clocks are stopped
15865        or when they have *just* been set or switched; otherwise
15866        it will be off by the time since the current tick started.
15867     */
15868     if (machineWhite) {
15869         time = whiteTimeRemaining / 10;
15870         otime = blackTimeRemaining / 10;
15871     } else {
15872         time = blackTimeRemaining / 10;
15873         otime = whiteTimeRemaining / 10;
15874     }
15875     /* [HGM] translate opponent's time by time-odds factor */
15876     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15877
15878     if (time <= 0) time = 1;
15879     if (otime <= 0) otime = 1;
15880
15881     snprintf(message, MSG_SIZ, "time %ld\n", time);
15882     SendToProgram(message, cps);
15883
15884     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15885     SendToProgram(message, cps);
15886 }
15887
15888 int
15889 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15890 {
15891   char buf[MSG_SIZ];
15892   int len = strlen(name);
15893   int val;
15894
15895   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15896     (*p) += len + 1;
15897     sscanf(*p, "%d", &val);
15898     *loc = (val != 0);
15899     while (**p && **p != ' ')
15900       (*p)++;
15901     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15902     SendToProgram(buf, cps);
15903     return TRUE;
15904   }
15905   return FALSE;
15906 }
15907
15908 int
15909 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15910 {
15911   char buf[MSG_SIZ];
15912   int len = strlen(name);
15913   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15914     (*p) += len + 1;
15915     sscanf(*p, "%d", loc);
15916     while (**p && **p != ' ') (*p)++;
15917     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15918     SendToProgram(buf, cps);
15919     return TRUE;
15920   }
15921   return FALSE;
15922 }
15923
15924 int
15925 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15926 {
15927   char buf[MSG_SIZ];
15928   int len = strlen(name);
15929   if (strncmp((*p), name, len) == 0
15930       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15931     (*p) += len + 2;
15932     sscanf(*p, "%[^\"]", loc);
15933     while (**p && **p != '\"') (*p)++;
15934     if (**p == '\"') (*p)++;
15935     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15936     SendToProgram(buf, cps);
15937     return TRUE;
15938   }
15939   return FALSE;
15940 }
15941
15942 int
15943 ParseOption (Option *opt, ChessProgramState *cps)
15944 // [HGM] options: process the string that defines an engine option, and determine
15945 // name, type, default value, and allowed value range
15946 {
15947         char *p, *q, buf[MSG_SIZ];
15948         int n, min = (-1)<<31, max = 1<<31, def;
15949
15950         if(p = strstr(opt->name, " -spin ")) {
15951             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15952             if(max < min) max = min; // enforce consistency
15953             if(def < min) def = min;
15954             if(def > max) def = max;
15955             opt->value = def;
15956             opt->min = min;
15957             opt->max = max;
15958             opt->type = Spin;
15959         } else if((p = strstr(opt->name, " -slider "))) {
15960             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15961             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15962             if(max < min) max = min; // enforce consistency
15963             if(def < min) def = min;
15964             if(def > max) def = max;
15965             opt->value = def;
15966             opt->min = min;
15967             opt->max = max;
15968             opt->type = Spin; // Slider;
15969         } else if((p = strstr(opt->name, " -string "))) {
15970             opt->textValue = p+9;
15971             opt->type = TextBox;
15972         } else if((p = strstr(opt->name, " -file "))) {
15973             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15974             opt->textValue = p+7;
15975             opt->type = FileName; // FileName;
15976         } else if((p = strstr(opt->name, " -path "))) {
15977             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15978             opt->textValue = p+7;
15979             opt->type = PathName; // PathName;
15980         } else if(p = strstr(opt->name, " -check ")) {
15981             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15982             opt->value = (def != 0);
15983             opt->type = CheckBox;
15984         } else if(p = strstr(opt->name, " -combo ")) {
15985             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15986             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15987             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15988             opt->value = n = 0;
15989             while(q = StrStr(q, " /// ")) {
15990                 n++; *q = 0;    // count choices, and null-terminate each of them
15991                 q += 5;
15992                 if(*q == '*') { // remember default, which is marked with * prefix
15993                     q++;
15994                     opt->value = n;
15995                 }
15996                 cps->comboList[cps->comboCnt++] = q;
15997             }
15998             cps->comboList[cps->comboCnt++] = NULL;
15999             opt->max = n + 1;
16000             opt->type = ComboBox;
16001         } else if(p = strstr(opt->name, " -button")) {
16002             opt->type = Button;
16003         } else if(p = strstr(opt->name, " -save")) {
16004             opt->type = SaveButton;
16005         } else return FALSE;
16006         *p = 0; // terminate option name
16007         // now look if the command-line options define a setting for this engine option.
16008         if(cps->optionSettings && cps->optionSettings[0])
16009             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16010         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16011           snprintf(buf, MSG_SIZ, "option %s", p);
16012                 if(p = strstr(buf, ",")) *p = 0;
16013                 if(q = strchr(buf, '=')) switch(opt->type) {
16014                     case ComboBox:
16015                         for(n=0; n<opt->max; n++)
16016                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16017                         break;
16018                     case TextBox:
16019                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16020                         break;
16021                     case Spin:
16022                     case CheckBox:
16023                         opt->value = atoi(q+1);
16024                     default:
16025                         break;
16026                 }
16027                 strcat(buf, "\n");
16028                 SendToProgram(buf, cps);
16029         }
16030         return TRUE;
16031 }
16032
16033 void
16034 FeatureDone (ChessProgramState *cps, int val)
16035 {
16036   DelayedEventCallback cb = GetDelayedEvent();
16037   if ((cb == InitBackEnd3 && cps == &first) ||
16038       (cb == SettingsMenuIfReady && cps == &second) ||
16039       (cb == LoadEngine) ||
16040       (cb == TwoMachinesEventIfReady)) {
16041     CancelDelayedEvent();
16042     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16043   }
16044   cps->initDone = val;
16045   if(val) cps->reload = FALSE;
16046 }
16047
16048 /* Parse feature command from engine */
16049 void
16050 ParseFeatures (char *args, ChessProgramState *cps)
16051 {
16052   char *p = args;
16053   char *q;
16054   int val;
16055   char buf[MSG_SIZ];
16056
16057   for (;;) {
16058     while (*p == ' ') p++;
16059     if (*p == NULLCHAR) return;
16060
16061     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16062     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16063     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16064     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16065     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16066     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16067     if (BoolFeature(&p, "reuse", &val, cps)) {
16068       /* Engine can disable reuse, but can't enable it if user said no */
16069       if (!val) cps->reuse = FALSE;
16070       continue;
16071     }
16072     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16073     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16074       if (gameMode == TwoMachinesPlay) {
16075         DisplayTwoMachinesTitle();
16076       } else {
16077         DisplayTitle("");
16078       }
16079       continue;
16080     }
16081     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16082     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16083     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16084     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16085     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16086     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16087     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16088     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16089     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16090     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16091     if (IntFeature(&p, "done", &val, cps)) {
16092       FeatureDone(cps, val);
16093       continue;
16094     }
16095     /* Added by Tord: */
16096     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16097     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16098     /* End of additions by Tord */
16099
16100     /* [HGM] added features: */
16101     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16102     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16103     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16104     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16105     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16106     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16107     if (StringFeature(&p, "option", buf, cps)) {
16108         if(cps->reload) continue; // we are reloading because of xreuse
16109         FREE(cps->option[cps->nrOptions].name);
16110         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16111         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16112         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16113           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16114             SendToProgram(buf, cps);
16115             continue;
16116         }
16117         if(cps->nrOptions >= MAX_OPTIONS) {
16118             cps->nrOptions--;
16119             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16120             DisplayError(buf, 0);
16121         }
16122         continue;
16123     }
16124     /* End of additions by HGM */
16125
16126     /* unknown feature: complain and skip */
16127     q = p;
16128     while (*q && *q != '=') q++;
16129     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16130     SendToProgram(buf, cps);
16131     p = q;
16132     if (*p == '=') {
16133       p++;
16134       if (*p == '\"') {
16135         p++;
16136         while (*p && *p != '\"') p++;
16137         if (*p == '\"') p++;
16138       } else {
16139         while (*p && *p != ' ') p++;
16140       }
16141     }
16142   }
16143
16144 }
16145
16146 void
16147 PeriodicUpdatesEvent (int newState)
16148 {
16149     if (newState == appData.periodicUpdates)
16150       return;
16151
16152     appData.periodicUpdates=newState;
16153
16154     /* Display type changes, so update it now */
16155 //    DisplayAnalysis();
16156
16157     /* Get the ball rolling again... */
16158     if (newState) {
16159         AnalysisPeriodicEvent(1);
16160         StartAnalysisClock();
16161     }
16162 }
16163
16164 void
16165 PonderNextMoveEvent (int newState)
16166 {
16167     if (newState == appData.ponderNextMove) return;
16168     if (gameMode == EditPosition) EditPositionDone(TRUE);
16169     if (newState) {
16170         SendToProgram("hard\n", &first);
16171         if (gameMode == TwoMachinesPlay) {
16172             SendToProgram("hard\n", &second);
16173         }
16174     } else {
16175         SendToProgram("easy\n", &first);
16176         thinkOutput[0] = NULLCHAR;
16177         if (gameMode == TwoMachinesPlay) {
16178             SendToProgram("easy\n", &second);
16179         }
16180     }
16181     appData.ponderNextMove = newState;
16182 }
16183
16184 void
16185 NewSettingEvent (int option, int *feature, char *command, int value)
16186 {
16187     char buf[MSG_SIZ];
16188
16189     if (gameMode == EditPosition) EditPositionDone(TRUE);
16190     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16191     if(feature == NULL || *feature) SendToProgram(buf, &first);
16192     if (gameMode == TwoMachinesPlay) {
16193         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16194     }
16195 }
16196
16197 void
16198 ShowThinkingEvent ()
16199 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16200 {
16201     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16202     int newState = appData.showThinking
16203         // [HGM] thinking: other features now need thinking output as well
16204         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16205
16206     if (oldState == newState) return;
16207     oldState = newState;
16208     if (gameMode == EditPosition) EditPositionDone(TRUE);
16209     if (oldState) {
16210         SendToProgram("post\n", &first);
16211         if (gameMode == TwoMachinesPlay) {
16212             SendToProgram("post\n", &second);
16213         }
16214     } else {
16215         SendToProgram("nopost\n", &first);
16216         thinkOutput[0] = NULLCHAR;
16217         if (gameMode == TwoMachinesPlay) {
16218             SendToProgram("nopost\n", &second);
16219         }
16220     }
16221 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16222 }
16223
16224 void
16225 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16226 {
16227   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16228   if (pr == NoProc) return;
16229   AskQuestion(title, question, replyPrefix, pr);
16230 }
16231
16232 void
16233 TypeInEvent (char firstChar)
16234 {
16235     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16236         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16237         gameMode == AnalyzeMode || gameMode == EditGame ||
16238         gameMode == EditPosition || gameMode == IcsExamining ||
16239         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16240         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16241                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16242                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16243         gameMode == Training) PopUpMoveDialog(firstChar);
16244 }
16245
16246 void
16247 TypeInDoneEvent (char *move)
16248 {
16249         Board board;
16250         int n, fromX, fromY, toX, toY;
16251         char promoChar;
16252         ChessMove moveType;
16253
16254         // [HGM] FENedit
16255         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16256                 EditPositionPasteFEN(move);
16257                 return;
16258         }
16259         // [HGM] movenum: allow move number to be typed in any mode
16260         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16261           ToNrEvent(2*n-1);
16262           return;
16263         }
16264         // undocumented kludge: allow command-line option to be typed in!
16265         // (potentially fatal, and does not implement the effect of the option.)
16266         // should only be used for options that are values on which future decisions will be made,
16267         // and definitely not on options that would be used during initialization.
16268         if(strstr(move, "!!! -") == move) {
16269             ParseArgsFromString(move+4);
16270             return;
16271         }
16272
16273       if (gameMode != EditGame && currentMove != forwardMostMove &&
16274         gameMode != Training) {
16275         DisplayMoveError(_("Displayed move is not current"));
16276       } else {
16277         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16278           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16279         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16280         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16281           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16282           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16283         } else {
16284           DisplayMoveError(_("Could not parse move"));
16285         }
16286       }
16287 }
16288
16289 void
16290 DisplayMove (int moveNumber)
16291 {
16292     char message[MSG_SIZ];
16293     char res[MSG_SIZ];
16294     char cpThinkOutput[MSG_SIZ];
16295
16296     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16297
16298     if (moveNumber == forwardMostMove - 1 ||
16299         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16300
16301         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16302
16303         if (strchr(cpThinkOutput, '\n')) {
16304             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16305         }
16306     } else {
16307         *cpThinkOutput = NULLCHAR;
16308     }
16309
16310     /* [AS] Hide thinking from human user */
16311     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16312         *cpThinkOutput = NULLCHAR;
16313         if( thinkOutput[0] != NULLCHAR ) {
16314             int i;
16315
16316             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16317                 cpThinkOutput[i] = '.';
16318             }
16319             cpThinkOutput[i] = NULLCHAR;
16320             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16321         }
16322     }
16323
16324     if (moveNumber == forwardMostMove - 1 &&
16325         gameInfo.resultDetails != NULL) {
16326         if (gameInfo.resultDetails[0] == NULLCHAR) {
16327           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16328         } else {
16329           snprintf(res, MSG_SIZ, " {%s} %s",
16330                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16331         }
16332     } else {
16333         res[0] = NULLCHAR;
16334     }
16335
16336     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16337         DisplayMessage(res, cpThinkOutput);
16338     } else {
16339       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16340                 WhiteOnMove(moveNumber) ? " " : ".. ",
16341                 parseList[moveNumber], res);
16342         DisplayMessage(message, cpThinkOutput);
16343     }
16344 }
16345
16346 void
16347 DisplayComment (int moveNumber, char *text)
16348 {
16349     char title[MSG_SIZ];
16350
16351     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16352       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16353     } else {
16354       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16355               WhiteOnMove(moveNumber) ? " " : ".. ",
16356               parseList[moveNumber]);
16357     }
16358     if (text != NULL && (appData.autoDisplayComment || commentUp))
16359         CommentPopUp(title, text);
16360 }
16361
16362 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16363  * might be busy thinking or pondering.  It can be omitted if your
16364  * gnuchess is configured to stop thinking immediately on any user
16365  * input.  However, that gnuchess feature depends on the FIONREAD
16366  * ioctl, which does not work properly on some flavors of Unix.
16367  */
16368 void
16369 Attention (ChessProgramState *cps)
16370 {
16371 #if ATTENTION
16372     if (!cps->useSigint) return;
16373     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16374     switch (gameMode) {
16375       case MachinePlaysWhite:
16376       case MachinePlaysBlack:
16377       case TwoMachinesPlay:
16378       case IcsPlayingWhite:
16379       case IcsPlayingBlack:
16380       case AnalyzeMode:
16381       case AnalyzeFile:
16382         /* Skip if we know it isn't thinking */
16383         if (!cps->maybeThinking) return;
16384         if (appData.debugMode)
16385           fprintf(debugFP, "Interrupting %s\n", cps->which);
16386         InterruptChildProcess(cps->pr);
16387         cps->maybeThinking = FALSE;
16388         break;
16389       default:
16390         break;
16391     }
16392 #endif /*ATTENTION*/
16393 }
16394
16395 int
16396 CheckFlags ()
16397 {
16398     if (whiteTimeRemaining <= 0) {
16399         if (!whiteFlag) {
16400             whiteFlag = TRUE;
16401             if (appData.icsActive) {
16402                 if (appData.autoCallFlag &&
16403                     gameMode == IcsPlayingBlack && !blackFlag) {
16404                   SendToICS(ics_prefix);
16405                   SendToICS("flag\n");
16406                 }
16407             } else {
16408                 if (blackFlag) {
16409                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16410                 } else {
16411                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16412                     if (appData.autoCallFlag) {
16413                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16414                         return TRUE;
16415                     }
16416                 }
16417             }
16418         }
16419     }
16420     if (blackTimeRemaining <= 0) {
16421         if (!blackFlag) {
16422             blackFlag = TRUE;
16423             if (appData.icsActive) {
16424                 if (appData.autoCallFlag &&
16425                     gameMode == IcsPlayingWhite && !whiteFlag) {
16426                   SendToICS(ics_prefix);
16427                   SendToICS("flag\n");
16428                 }
16429             } else {
16430                 if (whiteFlag) {
16431                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16432                 } else {
16433                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16434                     if (appData.autoCallFlag) {
16435                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16436                         return TRUE;
16437                     }
16438                 }
16439             }
16440         }
16441     }
16442     return FALSE;
16443 }
16444
16445 void
16446 CheckTimeControl ()
16447 {
16448     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16449         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16450
16451     /*
16452      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16453      */
16454     if ( !WhiteOnMove(forwardMostMove) ) {
16455         /* White made time control */
16456         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16457         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16458         /* [HGM] time odds: correct new time quota for time odds! */
16459                                             / WhitePlayer()->timeOdds;
16460         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16461     } else {
16462         lastBlack -= blackTimeRemaining;
16463         /* Black made time control */
16464         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16465                                             / WhitePlayer()->other->timeOdds;
16466         lastWhite = whiteTimeRemaining;
16467     }
16468 }
16469
16470 void
16471 DisplayBothClocks ()
16472 {
16473     int wom = gameMode == EditPosition ?
16474       !blackPlaysFirst : WhiteOnMove(currentMove);
16475     DisplayWhiteClock(whiteTimeRemaining, wom);
16476     DisplayBlackClock(blackTimeRemaining, !wom);
16477 }
16478
16479
16480 /* Timekeeping seems to be a portability nightmare.  I think everyone
16481    has ftime(), but I'm really not sure, so I'm including some ifdefs
16482    to use other calls if you don't.  Clocks will be less accurate if
16483    you have neither ftime nor gettimeofday.
16484 */
16485
16486 /* VS 2008 requires the #include outside of the function */
16487 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16488 #include <sys/timeb.h>
16489 #endif
16490
16491 /* Get the current time as a TimeMark */
16492 void
16493 GetTimeMark (TimeMark *tm)
16494 {
16495 #if HAVE_GETTIMEOFDAY
16496
16497     struct timeval timeVal;
16498     struct timezone timeZone;
16499
16500     gettimeofday(&timeVal, &timeZone);
16501     tm->sec = (long) timeVal.tv_sec;
16502     tm->ms = (int) (timeVal.tv_usec / 1000L);
16503
16504 #else /*!HAVE_GETTIMEOFDAY*/
16505 #if HAVE_FTIME
16506
16507 // include <sys/timeb.h> / moved to just above start of function
16508     struct timeb timeB;
16509
16510     ftime(&timeB);
16511     tm->sec = (long) timeB.time;
16512     tm->ms = (int) timeB.millitm;
16513
16514 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16515     tm->sec = (long) time(NULL);
16516     tm->ms = 0;
16517 #endif
16518 #endif
16519 }
16520
16521 /* Return the difference in milliseconds between two
16522    time marks.  We assume the difference will fit in a long!
16523 */
16524 long
16525 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16526 {
16527     return 1000L*(tm2->sec - tm1->sec) +
16528            (long) (tm2->ms - tm1->ms);
16529 }
16530
16531
16532 /*
16533  * Code to manage the game clocks.
16534  *
16535  * In tournament play, black starts the clock and then white makes a move.
16536  * We give the human user a slight advantage if he is playing white---the
16537  * clocks don't run until he makes his first move, so it takes zero time.
16538  * Also, we don't account for network lag, so we could get out of sync
16539  * with GNU Chess's clock -- but then, referees are always right.
16540  */
16541
16542 static TimeMark tickStartTM;
16543 static long intendedTickLength;
16544
16545 long
16546 NextTickLength (long timeRemaining)
16547 {
16548     long nominalTickLength, nextTickLength;
16549
16550     if (timeRemaining > 0L && timeRemaining <= 10000L)
16551       nominalTickLength = 100L;
16552     else
16553       nominalTickLength = 1000L;
16554     nextTickLength = timeRemaining % nominalTickLength;
16555     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16556
16557     return nextTickLength;
16558 }
16559
16560 /* Adjust clock one minute up or down */
16561 void
16562 AdjustClock (Boolean which, int dir)
16563 {
16564     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16565     if(which) blackTimeRemaining += 60000*dir;
16566     else      whiteTimeRemaining += 60000*dir;
16567     DisplayBothClocks();
16568     adjustedClock = TRUE;
16569 }
16570
16571 /* Stop clocks and reset to a fresh time control */
16572 void
16573 ResetClocks ()
16574 {
16575     (void) StopClockTimer();
16576     if (appData.icsActive) {
16577         whiteTimeRemaining = blackTimeRemaining = 0;
16578     } else if (searchTime) {
16579         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16580         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16581     } else { /* [HGM] correct new time quote for time odds */
16582         whiteTC = blackTC = fullTimeControlString;
16583         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16584         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16585     }
16586     if (whiteFlag || blackFlag) {
16587         DisplayTitle("");
16588         whiteFlag = blackFlag = FALSE;
16589     }
16590     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16591     DisplayBothClocks();
16592     adjustedClock = FALSE;
16593 }
16594
16595 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16596
16597 /* Decrement running clock by amount of time that has passed */
16598 void
16599 DecrementClocks ()
16600 {
16601     long timeRemaining;
16602     long lastTickLength, fudge;
16603     TimeMark now;
16604
16605     if (!appData.clockMode) return;
16606     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16607
16608     GetTimeMark(&now);
16609
16610     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16611
16612     /* Fudge if we woke up a little too soon */
16613     fudge = intendedTickLength - lastTickLength;
16614     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16615
16616     if (WhiteOnMove(forwardMostMove)) {
16617         if(whiteNPS >= 0) lastTickLength = 0;
16618         timeRemaining = whiteTimeRemaining -= lastTickLength;
16619         if(timeRemaining < 0 && !appData.icsActive) {
16620             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16621             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16622                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16623                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16624             }
16625         }
16626         DisplayWhiteClock(whiteTimeRemaining - fudge,
16627                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16628     } else {
16629         if(blackNPS >= 0) lastTickLength = 0;
16630         timeRemaining = blackTimeRemaining -= lastTickLength;
16631         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16632             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16633             if(suddenDeath) {
16634                 blackStartMove = forwardMostMove;
16635                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16636             }
16637         }
16638         DisplayBlackClock(blackTimeRemaining - fudge,
16639                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16640     }
16641     if (CheckFlags()) return;
16642
16643     if(twoBoards) { // count down secondary board's clocks as well
16644         activePartnerTime -= lastTickLength;
16645         partnerUp = 1;
16646         if(activePartner == 'W')
16647             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16648         else
16649             DisplayBlackClock(activePartnerTime, TRUE);
16650         partnerUp = 0;
16651     }
16652
16653     tickStartTM = now;
16654     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16655     StartClockTimer(intendedTickLength);
16656
16657     /* if the time remaining has fallen below the alarm threshold, sound the
16658      * alarm. if the alarm has sounded and (due to a takeback or time control
16659      * with increment) the time remaining has increased to a level above the
16660      * threshold, reset the alarm so it can sound again.
16661      */
16662
16663     if (appData.icsActive && appData.icsAlarm) {
16664
16665         /* make sure we are dealing with the user's clock */
16666         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16667                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16668            )) return;
16669
16670         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16671             alarmSounded = FALSE;
16672         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16673             PlayAlarmSound();
16674             alarmSounded = TRUE;
16675         }
16676     }
16677 }
16678
16679
16680 /* A player has just moved, so stop the previously running
16681    clock and (if in clock mode) start the other one.
16682    We redisplay both clocks in case we're in ICS mode, because
16683    ICS gives us an update to both clocks after every move.
16684    Note that this routine is called *after* forwardMostMove
16685    is updated, so the last fractional tick must be subtracted
16686    from the color that is *not* on move now.
16687 */
16688 void
16689 SwitchClocks (int newMoveNr)
16690 {
16691     long lastTickLength;
16692     TimeMark now;
16693     int flagged = FALSE;
16694
16695     GetTimeMark(&now);
16696
16697     if (StopClockTimer() && appData.clockMode) {
16698         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16699         if (!WhiteOnMove(forwardMostMove)) {
16700             if(blackNPS >= 0) lastTickLength = 0;
16701             blackTimeRemaining -= lastTickLength;
16702            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16703 //         if(pvInfoList[forwardMostMove].time == -1)
16704                  pvInfoList[forwardMostMove].time =               // use GUI time
16705                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16706         } else {
16707            if(whiteNPS >= 0) lastTickLength = 0;
16708            whiteTimeRemaining -= lastTickLength;
16709            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16710 //         if(pvInfoList[forwardMostMove].time == -1)
16711                  pvInfoList[forwardMostMove].time =
16712                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16713         }
16714         flagged = CheckFlags();
16715     }
16716     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16717     CheckTimeControl();
16718
16719     if (flagged || !appData.clockMode) return;
16720
16721     switch (gameMode) {
16722       case MachinePlaysBlack:
16723       case MachinePlaysWhite:
16724       case BeginningOfGame:
16725         if (pausing) return;
16726         break;
16727
16728       case EditGame:
16729       case PlayFromGameFile:
16730       case IcsExamining:
16731         return;
16732
16733       default:
16734         break;
16735     }
16736
16737     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16738         if(WhiteOnMove(forwardMostMove))
16739              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16740         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16741     }
16742
16743     tickStartTM = now;
16744     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16745       whiteTimeRemaining : blackTimeRemaining);
16746     StartClockTimer(intendedTickLength);
16747 }
16748
16749
16750 /* Stop both clocks */
16751 void
16752 StopClocks ()
16753 {
16754     long lastTickLength;
16755     TimeMark now;
16756
16757     if (!StopClockTimer()) return;
16758     if (!appData.clockMode) return;
16759
16760     GetTimeMark(&now);
16761
16762     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16763     if (WhiteOnMove(forwardMostMove)) {
16764         if(whiteNPS >= 0) lastTickLength = 0;
16765         whiteTimeRemaining -= lastTickLength;
16766         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16767     } else {
16768         if(blackNPS >= 0) lastTickLength = 0;
16769         blackTimeRemaining -= lastTickLength;
16770         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16771     }
16772     CheckFlags();
16773 }
16774
16775 /* Start clock of player on move.  Time may have been reset, so
16776    if clock is already running, stop and restart it. */
16777 void
16778 StartClocks ()
16779 {
16780     (void) StopClockTimer(); /* in case it was running already */
16781     DisplayBothClocks();
16782     if (CheckFlags()) return;
16783
16784     if (!appData.clockMode) return;
16785     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16786
16787     GetTimeMark(&tickStartTM);
16788     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16789       whiteTimeRemaining : blackTimeRemaining);
16790
16791    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16792     whiteNPS = blackNPS = -1;
16793     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16794        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16795         whiteNPS = first.nps;
16796     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16797        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16798         blackNPS = first.nps;
16799     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16800         whiteNPS = second.nps;
16801     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16802         blackNPS = second.nps;
16803     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16804
16805     StartClockTimer(intendedTickLength);
16806 }
16807
16808 char *
16809 TimeString (long ms)
16810 {
16811     long second, minute, hour, day;
16812     char *sign = "";
16813     static char buf[32];
16814
16815     if (ms > 0 && ms <= 9900) {
16816       /* convert milliseconds to tenths, rounding up */
16817       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16818
16819       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16820       return buf;
16821     }
16822
16823     /* convert milliseconds to seconds, rounding up */
16824     /* use floating point to avoid strangeness of integer division
16825        with negative dividends on many machines */
16826     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16827
16828     if (second < 0) {
16829         sign = "-";
16830         second = -second;
16831     }
16832
16833     day = second / (60 * 60 * 24);
16834     second = second % (60 * 60 * 24);
16835     hour = second / (60 * 60);
16836     second = second % (60 * 60);
16837     minute = second / 60;
16838     second = second % 60;
16839
16840     if (day > 0)
16841       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16842               sign, day, hour, minute, second);
16843     else if (hour > 0)
16844       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16845     else
16846       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16847
16848     return buf;
16849 }
16850
16851
16852 /*
16853  * This is necessary because some C libraries aren't ANSI C compliant yet.
16854  */
16855 char *
16856 StrStr (char *string, char *match)
16857 {
16858     int i, length;
16859
16860     length = strlen(match);
16861
16862     for (i = strlen(string) - length; i >= 0; i--, string++)
16863       if (!strncmp(match, string, length))
16864         return string;
16865
16866     return NULL;
16867 }
16868
16869 char *
16870 StrCaseStr (char *string, char *match)
16871 {
16872     int i, j, length;
16873
16874     length = strlen(match);
16875
16876     for (i = strlen(string) - length; i >= 0; i--, string++) {
16877         for (j = 0; j < length; j++) {
16878             if (ToLower(match[j]) != ToLower(string[j]))
16879               break;
16880         }
16881         if (j == length) return string;
16882     }
16883
16884     return NULL;
16885 }
16886
16887 #ifndef _amigados
16888 int
16889 StrCaseCmp (char *s1, char *s2)
16890 {
16891     char c1, c2;
16892
16893     for (;;) {
16894         c1 = ToLower(*s1++);
16895         c2 = ToLower(*s2++);
16896         if (c1 > c2) return 1;
16897         if (c1 < c2) return -1;
16898         if (c1 == NULLCHAR) return 0;
16899     }
16900 }
16901
16902
16903 int
16904 ToLower (int c)
16905 {
16906     return isupper(c) ? tolower(c) : c;
16907 }
16908
16909
16910 int
16911 ToUpper (int c)
16912 {
16913     return islower(c) ? toupper(c) : c;
16914 }
16915 #endif /* !_amigados    */
16916
16917 char *
16918 StrSave (char *s)
16919 {
16920   char *ret;
16921
16922   if ((ret = (char *) malloc(strlen(s) + 1)))
16923     {
16924       safeStrCpy(ret, s, strlen(s)+1);
16925     }
16926   return ret;
16927 }
16928
16929 char *
16930 StrSavePtr (char *s, char **savePtr)
16931 {
16932     if (*savePtr) {
16933         free(*savePtr);
16934     }
16935     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16936       safeStrCpy(*savePtr, s, strlen(s)+1);
16937     }
16938     return(*savePtr);
16939 }
16940
16941 char *
16942 PGNDate ()
16943 {
16944     time_t clock;
16945     struct tm *tm;
16946     char buf[MSG_SIZ];
16947
16948     clock = time((time_t *)NULL);
16949     tm = localtime(&clock);
16950     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16951             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16952     return StrSave(buf);
16953 }
16954
16955
16956 char *
16957 PositionToFEN (int move, char *overrideCastling)
16958 {
16959     int i, j, fromX, fromY, toX, toY;
16960     int whiteToPlay;
16961     char buf[MSG_SIZ];
16962     char *p, *q;
16963     int emptycount;
16964     ChessSquare piece;
16965
16966     whiteToPlay = (gameMode == EditPosition) ?
16967       !blackPlaysFirst : (move % 2 == 0);
16968     p = buf;
16969
16970     /* Piece placement data */
16971     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16972         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16973         emptycount = 0;
16974         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16975             if (boards[move][i][j] == EmptySquare) {
16976                 emptycount++;
16977             } else { ChessSquare piece = boards[move][i][j];
16978                 if (emptycount > 0) {
16979                     if(emptycount<10) /* [HGM] can be >= 10 */
16980                         *p++ = '0' + emptycount;
16981                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16982                     emptycount = 0;
16983                 }
16984                 if(PieceToChar(piece) == '+') {
16985                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16986                     *p++ = '+';
16987                     piece = (ChessSquare)(DEMOTED piece);
16988                 }
16989                 *p++ = PieceToChar(piece);
16990                 if(p[-1] == '~') {
16991                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16992                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16993                     *p++ = '~';
16994                 }
16995             }
16996         }
16997         if (emptycount > 0) {
16998             if(emptycount<10) /* [HGM] can be >= 10 */
16999                 *p++ = '0' + emptycount;
17000             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17001             emptycount = 0;
17002         }
17003         *p++ = '/';
17004     }
17005     *(p - 1) = ' ';
17006
17007     /* [HGM] print Crazyhouse or Shogi holdings */
17008     if( gameInfo.holdingsWidth ) {
17009         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17010         q = p;
17011         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17012             piece = boards[move][i][BOARD_WIDTH-1];
17013             if( piece != EmptySquare )
17014               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17015                   *p++ = PieceToChar(piece);
17016         }
17017         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17018             piece = boards[move][BOARD_HEIGHT-i-1][0];
17019             if( piece != EmptySquare )
17020               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17021                   *p++ = PieceToChar(piece);
17022         }
17023
17024         if( q == p ) *p++ = '-';
17025         *p++ = ']';
17026         *p++ = ' ';
17027     }
17028
17029     /* Active color */
17030     *p++ = whiteToPlay ? 'w' : 'b';
17031     *p++ = ' ';
17032
17033   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17034     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17035   } else {
17036   if(nrCastlingRights) {
17037      q = p;
17038      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17039        /* [HGM] write directly from rights */
17040            if(boards[move][CASTLING][2] != NoRights &&
17041               boards[move][CASTLING][0] != NoRights   )
17042                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17043            if(boards[move][CASTLING][2] != NoRights &&
17044               boards[move][CASTLING][1] != NoRights   )
17045                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17046            if(boards[move][CASTLING][5] != NoRights &&
17047               boards[move][CASTLING][3] != NoRights   )
17048                 *p++ = boards[move][CASTLING][3] + AAA;
17049            if(boards[move][CASTLING][5] != NoRights &&
17050               boards[move][CASTLING][4] != NoRights   )
17051                 *p++ = boards[move][CASTLING][4] + AAA;
17052      } else {
17053
17054         /* [HGM] write true castling rights */
17055         if( nrCastlingRights == 6 ) {
17056             int q, k=0;
17057             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17058                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17059             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17060                  boards[move][CASTLING][2] != NoRights  );
17061             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17062                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17063                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17064                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17065                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17066             }
17067             if(q) *p++ = 'Q';
17068             k = 0;
17069             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17070                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17071             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17072                  boards[move][CASTLING][5] != NoRights  );
17073             if(gameInfo.variant == VariantSChess) {
17074                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17075                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17076                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17077                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17078             }
17079             if(q) *p++ = 'q';
17080         }
17081      }
17082      if (q == p) *p++ = '-'; /* No castling rights */
17083      *p++ = ' ';
17084   }
17085
17086   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17087      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17088     /* En passant target square */
17089     if (move > backwardMostMove) {
17090         fromX = moveList[move - 1][0] - AAA;
17091         fromY = moveList[move - 1][1] - ONE;
17092         toX = moveList[move - 1][2] - AAA;
17093         toY = moveList[move - 1][3] - ONE;
17094         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17095             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17096             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17097             fromX == toX) {
17098             /* 2-square pawn move just happened */
17099             *p++ = toX + AAA;
17100             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17101         } else {
17102             *p++ = '-';
17103         }
17104     } else if(move == backwardMostMove) {
17105         // [HGM] perhaps we should always do it like this, and forget the above?
17106         if((signed char)boards[move][EP_STATUS] >= 0) {
17107             *p++ = boards[move][EP_STATUS] + AAA;
17108             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17109         } else {
17110             *p++ = '-';
17111         }
17112     } else {
17113         *p++ = '-';
17114     }
17115     *p++ = ' ';
17116   }
17117   }
17118
17119     /* [HGM] find reversible plies */
17120     {   int i = 0, j=move;
17121
17122         if (appData.debugMode) { int k;
17123             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17124             for(k=backwardMostMove; k<=forwardMostMove; k++)
17125                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17126
17127         }
17128
17129         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17130         if( j == backwardMostMove ) i += initialRulePlies;
17131         sprintf(p, "%d ", i);
17132         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17133     }
17134     /* Fullmove number */
17135     sprintf(p, "%d", (move / 2) + 1);
17136
17137     return StrSave(buf);
17138 }
17139
17140 Boolean
17141 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17142 {
17143     int i, j;
17144     char *p, c;
17145     int emptycount, virgin[BOARD_FILES];
17146     ChessSquare piece;
17147
17148     p = fen;
17149
17150     /* [HGM] by default clear Crazyhouse holdings, if present */
17151     if(gameInfo.holdingsWidth) {
17152        for(i=0; i<BOARD_HEIGHT; i++) {
17153            board[i][0]             = EmptySquare; /* black holdings */
17154            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17155            board[i][1]             = (ChessSquare) 0; /* black counts */
17156            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17157        }
17158     }
17159
17160     /* Piece placement data */
17161     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17162         j = 0;
17163         for (;;) {
17164             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17165                 if (*p == '/') p++;
17166                 emptycount = gameInfo.boardWidth - j;
17167                 while (emptycount--)
17168                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17169                 break;
17170 #if(BOARD_FILES >= 10)
17171             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17172                 p++; emptycount=10;
17173                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17174                 while (emptycount--)
17175                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17176 #endif
17177             } else if (isdigit(*p)) {
17178                 emptycount = *p++ - '0';
17179                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17180                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17181                 while (emptycount--)
17182                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17183             } else if (*p == '+' || isalpha(*p)) {
17184                 if (j >= gameInfo.boardWidth) return FALSE;
17185                 if(*p=='+') {
17186                     piece = CharToPiece(*++p);
17187                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17188                     piece = (ChessSquare) (PROMOTED piece ); p++;
17189                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17190                 } else piece = CharToPiece(*p++);
17191
17192                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17193                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17194                     piece = (ChessSquare) (PROMOTED piece);
17195                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17196                     p++;
17197                 }
17198                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17199             } else {
17200                 return FALSE;
17201             }
17202         }
17203     }
17204     while (*p == '/' || *p == ' ') p++;
17205
17206     /* [HGM] look for Crazyhouse holdings here */
17207     while(*p==' ') p++;
17208     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17209         if(*p == '[') p++;
17210         if(*p == '-' ) p++; /* empty holdings */ else {
17211             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17212             /* if we would allow FEN reading to set board size, we would   */
17213             /* have to add holdings and shift the board read so far here   */
17214             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17215                 p++;
17216                 if((int) piece >= (int) BlackPawn ) {
17217                     i = (int)piece - (int)BlackPawn;
17218                     i = PieceToNumber((ChessSquare)i);
17219                     if( i >= gameInfo.holdingsSize ) return FALSE;
17220                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17221                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17222                 } else {
17223                     i = (int)piece - (int)WhitePawn;
17224                     i = PieceToNumber((ChessSquare)i);
17225                     if( i >= gameInfo.holdingsSize ) return FALSE;
17226                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17227                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17228                 }
17229             }
17230         }
17231         if(*p == ']') p++;
17232     }
17233
17234     while(*p == ' ') p++;
17235
17236     /* Active color */
17237     c = *p++;
17238     if(appData.colorNickNames) {
17239       if( c == appData.colorNickNames[0] ) c = 'w'; else
17240       if( c == appData.colorNickNames[1] ) c = 'b';
17241     }
17242     switch (c) {
17243       case 'w':
17244         *blackPlaysFirst = FALSE;
17245         break;
17246       case 'b':
17247         *blackPlaysFirst = TRUE;
17248         break;
17249       default:
17250         return FALSE;
17251     }
17252
17253     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17254     /* return the extra info in global variiables             */
17255
17256     /* set defaults in case FEN is incomplete */
17257     board[EP_STATUS] = EP_UNKNOWN;
17258     for(i=0; i<nrCastlingRights; i++ ) {
17259         board[CASTLING][i] =
17260             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17261     }   /* assume possible unless obviously impossible */
17262     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17263     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17264     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17265                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17266     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17267     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17268     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17269                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17270     FENrulePlies = 0;
17271
17272     while(*p==' ') p++;
17273     if(nrCastlingRights) {
17274       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17275       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17276           /* castling indicator present, so default becomes no castlings */
17277           for(i=0; i<nrCastlingRights; i++ ) {
17278                  board[CASTLING][i] = NoRights;
17279           }
17280       }
17281       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17282              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17283              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17284              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17285         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17286
17287         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17288             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17289             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17290         }
17291         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17292             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17293         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17294                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17295         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17296                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17297         switch(c) {
17298           case'K':
17299               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17300               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17301               board[CASTLING][2] = whiteKingFile;
17302               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17303               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17304               break;
17305           case'Q':
17306               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17307               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17308               board[CASTLING][2] = whiteKingFile;
17309               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17310               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17311               break;
17312           case'k':
17313               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17314               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17315               board[CASTLING][5] = blackKingFile;
17316               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17317               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17318               break;
17319           case'q':
17320               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17321               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17322               board[CASTLING][5] = blackKingFile;
17323               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17324               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17325           case '-':
17326               break;
17327           default: /* FRC castlings */
17328               if(c >= 'a') { /* black rights */
17329                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17330                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17331                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17332                   if(i == BOARD_RGHT) break;
17333                   board[CASTLING][5] = i;
17334                   c -= AAA;
17335                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17336                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17337                   if(c > i)
17338                       board[CASTLING][3] = c;
17339                   else
17340                       board[CASTLING][4] = c;
17341               } else { /* white rights */
17342                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17343                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17344                     if(board[0][i] == WhiteKing) break;
17345                   if(i == BOARD_RGHT) break;
17346                   board[CASTLING][2] = i;
17347                   c -= AAA - 'a' + 'A';
17348                   if(board[0][c] >= WhiteKing) break;
17349                   if(c > i)
17350                       board[CASTLING][0] = c;
17351                   else
17352                       board[CASTLING][1] = c;
17353               }
17354         }
17355       }
17356       for(i=0; i<nrCastlingRights; i++)
17357         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17358       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17359     if (appData.debugMode) {
17360         fprintf(debugFP, "FEN castling rights:");
17361         for(i=0; i<nrCastlingRights; i++)
17362         fprintf(debugFP, " %d", board[CASTLING][i]);
17363         fprintf(debugFP, "\n");
17364     }
17365
17366       while(*p==' ') p++;
17367     }
17368
17369     /* read e.p. field in games that know e.p. capture */
17370     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17371        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17372       if(*p=='-') {
17373         p++; board[EP_STATUS] = EP_NONE;
17374       } else {
17375          char c = *p++ - AAA;
17376
17377          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17378          if(*p >= '0' && *p <='9') p++;
17379          board[EP_STATUS] = c;
17380       }
17381     }
17382
17383
17384     if(sscanf(p, "%d", &i) == 1) {
17385         FENrulePlies = i; /* 50-move ply counter */
17386         /* (The move number is still ignored)    */
17387     }
17388
17389     return TRUE;
17390 }
17391
17392 void
17393 EditPositionPasteFEN (char *fen)
17394 {
17395   if (fen != NULL) {
17396     Board initial_position;
17397
17398     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17399       DisplayError(_("Bad FEN position in clipboard"), 0);
17400       return ;
17401     } else {
17402       int savedBlackPlaysFirst = blackPlaysFirst;
17403       EditPositionEvent();
17404       blackPlaysFirst = savedBlackPlaysFirst;
17405       CopyBoard(boards[0], initial_position);
17406       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17407       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17408       DisplayBothClocks();
17409       DrawPosition(FALSE, boards[currentMove]);
17410     }
17411   }
17412 }
17413
17414 static char cseq[12] = "\\   ";
17415
17416 Boolean
17417 set_cont_sequence (char *new_seq)
17418 {
17419     int len;
17420     Boolean ret;
17421
17422     // handle bad attempts to set the sequence
17423         if (!new_seq)
17424                 return 0; // acceptable error - no debug
17425
17426     len = strlen(new_seq);
17427     ret = (len > 0) && (len < sizeof(cseq));
17428     if (ret)
17429       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17430     else if (appData.debugMode)
17431       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17432     return ret;
17433 }
17434
17435 /*
17436     reformat a source message so words don't cross the width boundary.  internal
17437     newlines are not removed.  returns the wrapped size (no null character unless
17438     included in source message).  If dest is NULL, only calculate the size required
17439     for the dest buffer.  lp argument indicats line position upon entry, and it's
17440     passed back upon exit.
17441 */
17442 int
17443 wrap (char *dest, char *src, int count, int width, int *lp)
17444 {
17445     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17446
17447     cseq_len = strlen(cseq);
17448     old_line = line = *lp;
17449     ansi = len = clen = 0;
17450
17451     for (i=0; i < count; i++)
17452     {
17453         if (src[i] == '\033')
17454             ansi = 1;
17455
17456         // if we hit the width, back up
17457         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17458         {
17459             // store i & len in case the word is too long
17460             old_i = i, old_len = len;
17461
17462             // find the end of the last word
17463             while (i && src[i] != ' ' && src[i] != '\n')
17464             {
17465                 i--;
17466                 len--;
17467             }
17468
17469             // word too long?  restore i & len before splitting it
17470             if ((old_i-i+clen) >= width)
17471             {
17472                 i = old_i;
17473                 len = old_len;
17474             }
17475
17476             // extra space?
17477             if (i && src[i-1] == ' ')
17478                 len--;
17479
17480             if (src[i] != ' ' && src[i] != '\n')
17481             {
17482                 i--;
17483                 if (len)
17484                     len--;
17485             }
17486
17487             // now append the newline and continuation sequence
17488             if (dest)
17489                 dest[len] = '\n';
17490             len++;
17491             if (dest)
17492                 strncpy(dest+len, cseq, cseq_len);
17493             len += cseq_len;
17494             line = cseq_len;
17495             clen = cseq_len;
17496             continue;
17497         }
17498
17499         if (dest)
17500             dest[len] = src[i];
17501         len++;
17502         if (!ansi)
17503             line++;
17504         if (src[i] == '\n')
17505             line = 0;
17506         if (src[i] == 'm')
17507             ansi = 0;
17508     }
17509     if (dest && appData.debugMode)
17510     {
17511         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17512             count, width, line, len, *lp);
17513         show_bytes(debugFP, src, count);
17514         fprintf(debugFP, "\ndest: ");
17515         show_bytes(debugFP, dest, len);
17516         fprintf(debugFP, "\n");
17517     }
17518     *lp = dest ? line : old_line;
17519
17520     return len;
17521 }
17522
17523 // [HGM] vari: routines for shelving variations
17524 Boolean modeRestore = FALSE;
17525
17526 void
17527 PushInner (int firstMove, int lastMove)
17528 {
17529         int i, j, nrMoves = lastMove - firstMove;
17530
17531         // push current tail of game on stack
17532         savedResult[storedGames] = gameInfo.result;
17533         savedDetails[storedGames] = gameInfo.resultDetails;
17534         gameInfo.resultDetails = NULL;
17535         savedFirst[storedGames] = firstMove;
17536         savedLast [storedGames] = lastMove;
17537         savedFramePtr[storedGames] = framePtr;
17538         framePtr -= nrMoves; // reserve space for the boards
17539         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17540             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17541             for(j=0; j<MOVE_LEN; j++)
17542                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17543             for(j=0; j<2*MOVE_LEN; j++)
17544                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17545             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17546             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17547             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17548             pvInfoList[firstMove+i-1].depth = 0;
17549             commentList[framePtr+i] = commentList[firstMove+i];
17550             commentList[firstMove+i] = NULL;
17551         }
17552
17553         storedGames++;
17554         forwardMostMove = firstMove; // truncate game so we can start variation
17555 }
17556
17557 void
17558 PushTail (int firstMove, int lastMove)
17559 {
17560         if(appData.icsActive) { // only in local mode
17561                 forwardMostMove = currentMove; // mimic old ICS behavior
17562                 return;
17563         }
17564         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17565
17566         PushInner(firstMove, lastMove);
17567         if(storedGames == 1) GreyRevert(FALSE);
17568         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17569 }
17570
17571 void
17572 PopInner (Boolean annotate)
17573 {
17574         int i, j, nrMoves;
17575         char buf[8000], moveBuf[20];
17576
17577         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17578         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17579         nrMoves = savedLast[storedGames] - currentMove;
17580         if(annotate) {
17581                 int cnt = 10;
17582                 if(!WhiteOnMove(currentMove))
17583                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17584                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17585                 for(i=currentMove; i<forwardMostMove; i++) {
17586                         if(WhiteOnMove(i))
17587                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17588                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17589                         strcat(buf, moveBuf);
17590                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17591                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17592                 }
17593                 strcat(buf, ")");
17594         }
17595         for(i=1; i<=nrMoves; i++) { // copy last variation back
17596             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17597             for(j=0; j<MOVE_LEN; j++)
17598                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17599             for(j=0; j<2*MOVE_LEN; j++)
17600                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17601             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17602             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17603             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17604             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17605             commentList[currentMove+i] = commentList[framePtr+i];
17606             commentList[framePtr+i] = NULL;
17607         }
17608         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17609         framePtr = savedFramePtr[storedGames];
17610         gameInfo.result = savedResult[storedGames];
17611         if(gameInfo.resultDetails != NULL) {
17612             free(gameInfo.resultDetails);
17613       }
17614         gameInfo.resultDetails = savedDetails[storedGames];
17615         forwardMostMove = currentMove + nrMoves;
17616 }
17617
17618 Boolean
17619 PopTail (Boolean annotate)
17620 {
17621         if(appData.icsActive) return FALSE; // only in local mode
17622         if(!storedGames) return FALSE; // sanity
17623         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17624
17625         PopInner(annotate);
17626         if(currentMove < forwardMostMove) ForwardEvent(); else
17627         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17628
17629         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17630         return TRUE;
17631 }
17632
17633 void
17634 CleanupTail ()
17635 {       // remove all shelved variations
17636         int i;
17637         for(i=0; i<storedGames; i++) {
17638             if(savedDetails[i])
17639                 free(savedDetails[i]);
17640             savedDetails[i] = NULL;
17641         }
17642         for(i=framePtr; i<MAX_MOVES; i++) {
17643                 if(commentList[i]) free(commentList[i]);
17644                 commentList[i] = NULL;
17645         }
17646         framePtr = MAX_MOVES-1;
17647         storedGames = 0;
17648 }
17649
17650 void
17651 LoadVariation (int index, char *text)
17652 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17653         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17654         int level = 0, move;
17655
17656         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17657         // first find outermost bracketing variation
17658         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17659             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17660                 if(*p == '{') wait = '}'; else
17661                 if(*p == '[') wait = ']'; else
17662                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17663                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17664             }
17665             if(*p == wait) wait = NULLCHAR; // closing ]} found
17666             p++;
17667         }
17668         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17669         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17670         end[1] = NULLCHAR; // clip off comment beyond variation
17671         ToNrEvent(currentMove-1);
17672         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17673         // kludge: use ParsePV() to append variation to game
17674         move = currentMove;
17675         ParsePV(start, TRUE, TRUE);
17676         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17677         ClearPremoveHighlights();
17678         CommentPopDown();
17679         ToNrEvent(currentMove+1);
17680 }
17681
17682 void
17683 LoadTheme ()
17684 {
17685     char *p, *q, buf[MSG_SIZ];
17686     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17687         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17688         ParseArgsFromString(buf);
17689         ActivateTheme(TRUE); // also redo colors
17690         return;
17691     }
17692     p = nickName;
17693     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17694     {
17695         int len;
17696         q = appData.themeNames;
17697         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17698       if(appData.useBitmaps) {
17699         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17700                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17701                 appData.liteBackTextureMode,
17702                 appData.darkBackTextureMode );
17703       } else {
17704         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17705                 Col2Text(2),   // lightSquareColor
17706                 Col2Text(3) ); // darkSquareColor
17707       }
17708       if(appData.useBorder) {
17709         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17710                 appData.border);
17711       } else {
17712         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17713       }
17714       if(appData.useFont) {
17715         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17716                 appData.renderPiecesWithFont,
17717                 appData.fontToPieceTable,
17718                 Col2Text(9),    // appData.fontBackColorWhite
17719                 Col2Text(10) ); // appData.fontForeColorBlack
17720       } else {
17721         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17722                 appData.pieceDirectory);
17723         if(!appData.pieceDirectory[0])
17724           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17725                 Col2Text(0),   // whitePieceColor
17726                 Col2Text(1) ); // blackPieceColor
17727       }
17728       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17729                 Col2Text(4),   // highlightSquareColor
17730                 Col2Text(5) ); // premoveHighlightColor
17731         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17732         if(insert != q) insert[-1] = NULLCHAR;
17733         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17734         if(q)   free(q);
17735     }
17736     ActivateTheme(FALSE);
17737 }