Preserve mode on engine loading (sometimes)
[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 GameMode oldMode;
844
845 void
846 LoadEngine ()
847 {
848     int i;
849     if(WaitForEngine(savCps, LoadEngine)) return;
850     CommonEngineInit(); // recalculate time odds
851     if(gameInfo.variant != StringToVariant(appData.variant)) {
852         // we changed variant when loading the engine; this forces us to reset
853         Reset(TRUE, savCps != &first);
854         oldMode = BeginningOfGame; // to prevent restoring old mode
855     }
856     InitChessProgram(savCps, FALSE);
857     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
858     DisplayMessage("", "");
859     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
861     ThawUI();
862     SetGNUMode();
863     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
864 }
865
866 void
867 ReplaceEngine (ChessProgramState *cps, int n)
868 {
869     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
870     if(oldMode != BeginningOfGame) EditGameEvent();
871     UnloadEngine(cps);
872     appData.noChessProgram = FALSE;
873     appData.clockMode = TRUE;
874     InitEngine(cps, n);
875     UpdateLogos(TRUE);
876     if(n) return; // only startup first engine immediately; second can wait
877     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
878     LoadEngine();
879 }
880
881 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
882 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
883
884 static char resetOptions[] =
885         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
886         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
887         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
888         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
889
890 void
891 FloatToFront(char **list, char *engineLine)
892 {
893     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
894     int i=0;
895     if(appData.recentEngines <= 0) return;
896     TidyProgramName(engineLine, "localhost", tidy+1);
897     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
898     strncpy(buf+1, *list, MSG_SIZ-50);
899     if(p = strstr(buf, tidy)) { // tidy name appears in list
900         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
901         while(*p++ = *++q); // squeeze out
902     }
903     strcat(tidy, buf+1); // put list behind tidy name
904     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
905     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
906     ASSIGN(*list, tidy+1);
907 }
908
909 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
910
911 void
912 Load (ChessProgramState *cps, int i)
913 {
914     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
915     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
916         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
917         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
918         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
919         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
920         appData.firstProtocolVersion = PROTOVER;
921         ParseArgsFromString(buf);
922         SwapEngines(i);
923         ReplaceEngine(cps, i);
924         FloatToFront(&appData.recentEngineList, engineLine);
925         return;
926     }
927     p = engineName;
928     while(q = strchr(p, SLASH)) p = q+1;
929     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
930     if(engineDir[0] != NULLCHAR) {
931         ASSIGN(appData.directory[i], engineDir); p = engineName;
932     } else if(p != engineName) { // derive directory from engine path, when not given
933         p[-1] = 0;
934         ASSIGN(appData.directory[i], engineName);
935         p[-1] = SLASH;
936         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
937     } else { ASSIGN(appData.directory[i], "."); }
938     if(params[0]) {
939         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
940         snprintf(command, MSG_SIZ, "%s %s", p, params);
941         p = command;
942     }
943     ASSIGN(appData.chessProgram[i], p);
944     appData.isUCI[i] = isUCI;
945     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
946     appData.hasOwnBookUCI[i] = hasBook;
947     if(!nickName[0]) useNick = FALSE;
948     if(useNick) ASSIGN(appData.pgnName[i], nickName);
949     if(addToList) {
950         int len;
951         char quote;
952         q = firstChessProgramNames;
953         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
954         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
955         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
956                         quote, p, quote, appData.directory[i],
957                         useNick ? " -fn \"" : "",
958                         useNick ? nickName : "",
959                         useNick ? "\"" : "",
960                         v1 ? " -firstProtocolVersion 1" : "",
961                         hasBook ? "" : " -fNoOwnBookUCI",
962                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
963                         storeVariant ? " -variant " : "",
964                         storeVariant ? VariantName(gameInfo.variant) : "");
965         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
966         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
967         if(insert != q) insert[-1] = NULLCHAR;
968         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
969         if(q)   free(q);
970         FloatToFront(&appData.recentEngineList, buf);
971     }
972     ReplaceEngine(cps, i);
973 }
974
975 void
976 InitTimeControls ()
977 {
978     int matched, min, sec;
979     /*
980      * Parse timeControl resource
981      */
982     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
983                           appData.movesPerSession)) {
984         char buf[MSG_SIZ];
985         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
986         DisplayFatalError(buf, 0, 2);
987     }
988
989     /*
990      * Parse searchTime resource
991      */
992     if (*appData.searchTime != NULLCHAR) {
993         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
994         if (matched == 1) {
995             searchTime = min * 60;
996         } else if (matched == 2) {
997             searchTime = min * 60 + sec;
998         } else {
999             char buf[MSG_SIZ];
1000             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1001             DisplayFatalError(buf, 0, 2);
1002         }
1003     }
1004 }
1005
1006 void
1007 InitBackEnd1 ()
1008 {
1009
1010     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1011     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1012
1013     GetTimeMark(&programStartTime);
1014     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1015     appData.seedBase = random() + (random()<<15);
1016     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1017
1018     ClearProgramStats();
1019     programStats.ok_to_send = 1;
1020     programStats.seen_stat = 0;
1021
1022     /*
1023      * Initialize game list
1024      */
1025     ListNew(&gameList);
1026
1027
1028     /*
1029      * Internet chess server status
1030      */
1031     if (appData.icsActive) {
1032         appData.matchMode = FALSE;
1033         appData.matchGames = 0;
1034 #if ZIPPY
1035         appData.noChessProgram = !appData.zippyPlay;
1036 #else
1037         appData.zippyPlay = FALSE;
1038         appData.zippyTalk = FALSE;
1039         appData.noChessProgram = TRUE;
1040 #endif
1041         if (*appData.icsHelper != NULLCHAR) {
1042             appData.useTelnet = TRUE;
1043             appData.telnetProgram = appData.icsHelper;
1044         }
1045     } else {
1046         appData.zippyTalk = appData.zippyPlay = FALSE;
1047     }
1048
1049     /* [AS] Initialize pv info list [HGM] and game state */
1050     {
1051         int i, j;
1052
1053         for( i=0; i<=framePtr; i++ ) {
1054             pvInfoList[i].depth = -1;
1055             boards[i][EP_STATUS] = EP_NONE;
1056             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1057         }
1058     }
1059
1060     InitTimeControls();
1061
1062     /* [AS] Adjudication threshold */
1063     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1064
1065     InitEngine(&first, 0);
1066     InitEngine(&second, 1);
1067     CommonEngineInit();
1068
1069     pairing.which = "pairing"; // pairing engine
1070     pairing.pr = NoProc;
1071     pairing.isr = NULL;
1072     pairing.program = appData.pairingEngine;
1073     pairing.host = "localhost";
1074     pairing.dir = ".";
1075
1076     if (appData.icsActive) {
1077         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1078     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1079         appData.clockMode = FALSE;
1080         first.sendTime = second.sendTime = 0;
1081     }
1082
1083 #if ZIPPY
1084     /* Override some settings from environment variables, for backward
1085        compatibility.  Unfortunately it's not feasible to have the env
1086        vars just set defaults, at least in xboard.  Ugh.
1087     */
1088     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1089       ZippyInit();
1090     }
1091 #endif
1092
1093     if (!appData.icsActive) {
1094       char buf[MSG_SIZ];
1095       int len;
1096
1097       /* Check for variants that are supported only in ICS mode,
1098          or not at all.  Some that are accepted here nevertheless
1099          have bugs; see comments below.
1100       */
1101       VariantClass variant = StringToVariant(appData.variant);
1102       switch (variant) {
1103       case VariantBughouse:     /* need four players and two boards */
1104       case VariantKriegspiel:   /* need to hide pieces and move details */
1105         /* case VariantFischeRandom: (Fabien: moved below) */
1106         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1107         if( (len >= MSG_SIZ) && appData.debugMode )
1108           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1109
1110         DisplayFatalError(buf, 0, 2);
1111         return;
1112
1113       case VariantUnknown:
1114       case VariantLoadable:
1115       case Variant29:
1116       case Variant30:
1117       case Variant31:
1118       case Variant32:
1119       case Variant33:
1120       case Variant34:
1121       case Variant35:
1122       case Variant36:
1123       default:
1124         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1125         if( (len >= MSG_SIZ) && appData.debugMode )
1126           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1127
1128         DisplayFatalError(buf, 0, 2);
1129         return;
1130
1131       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1132       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1133       case VariantGothic:     /* [HGM] should work */
1134       case VariantCapablanca: /* [HGM] should work */
1135       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1136       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1137       case VariantKnightmate: /* [HGM] should work */
1138       case VariantCylinder:   /* [HGM] untested */
1139       case VariantFalcon:     /* [HGM] untested */
1140       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1141                                  offboard interposition not understood */
1142       case VariantNormal:     /* definitely works! */
1143       case VariantWildCastle: /* pieces not automatically shuffled */
1144       case VariantNoCastle:   /* pieces not automatically shuffled */
1145       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1146       case VariantLosers:     /* should work except for win condition,
1147                                  and doesn't know captures are mandatory */
1148       case VariantSuicide:    /* should work except for win condition,
1149                                  and doesn't know captures are mandatory */
1150       case VariantGiveaway:   /* should work except for win condition,
1151                                  and doesn't know captures are mandatory */
1152       case VariantTwoKings:   /* should work */
1153       case VariantAtomic:     /* should work except for win condition */
1154       case Variant3Check:     /* should work except for win condition */
1155       case VariantShatranj:   /* should work except for all win conditions */
1156       case VariantMakruk:     /* should work except for draw countdown */
1157       case VariantBerolina:   /* might work if TestLegality is off */
1158       case VariantCapaRandom: /* should work */
1159       case VariantJanus:      /* should work */
1160       case VariantSuper:      /* experimental */
1161       case VariantGreat:      /* experimental, requires legality testing to be off */
1162       case VariantSChess:     /* S-Chess, should work */
1163       case VariantGrand:      /* should work */
1164       case VariantSpartan:    /* should work */
1165         break;
1166       }
1167     }
1168
1169 }
1170
1171 int
1172 NextIntegerFromString (char ** str, long * value)
1173 {
1174     int result = -1;
1175     char * s = *str;
1176
1177     while( *s == ' ' || *s == '\t' ) {
1178         s++;
1179     }
1180
1181     *value = 0;
1182
1183     if( *s >= '0' && *s <= '9' ) {
1184         while( *s >= '0' && *s <= '9' ) {
1185             *value = *value * 10 + (*s - '0');
1186             s++;
1187         }
1188
1189         result = 0;
1190     }
1191
1192     *str = s;
1193
1194     return result;
1195 }
1196
1197 int
1198 NextTimeControlFromString (char ** str, long * value)
1199 {
1200     long temp;
1201     int result = NextIntegerFromString( str, &temp );
1202
1203     if( result == 0 ) {
1204         *value = temp * 60; /* Minutes */
1205         if( **str == ':' ) {
1206             (*str)++;
1207             result = NextIntegerFromString( str, &temp );
1208             *value += temp; /* Seconds */
1209         }
1210     }
1211
1212     return result;
1213 }
1214
1215 int
1216 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1217 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1218     int result = -1, type = 0; long temp, temp2;
1219
1220     if(**str != ':') return -1; // old params remain in force!
1221     (*str)++;
1222     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1223     if( NextIntegerFromString( str, &temp ) ) return -1;
1224     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1225
1226     if(**str != '/') {
1227         /* time only: incremental or sudden-death time control */
1228         if(**str == '+') { /* increment follows; read it */
1229             (*str)++;
1230             if(**str == '!') type = *(*str)++; // Bronstein TC
1231             if(result = NextIntegerFromString( str, &temp2)) return -1;
1232             *inc = temp2 * 1000;
1233             if(**str == '.') { // read fraction of increment
1234                 char *start = ++(*str);
1235                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1236                 temp2 *= 1000;
1237                 while(start++ < *str) temp2 /= 10;
1238                 *inc += temp2;
1239             }
1240         } else *inc = 0;
1241         *moves = 0; *tc = temp * 1000; *incType = type;
1242         return 0;
1243     }
1244
1245     (*str)++; /* classical time control */
1246     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1247
1248     if(result == 0) {
1249         *moves = temp;
1250         *tc    = temp2 * 1000;
1251         *inc   = 0;
1252         *incType = type;
1253     }
1254     return result;
1255 }
1256
1257 int
1258 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1259 {   /* [HGM] get time to add from the multi-session time-control string */
1260     int incType, moves=1; /* kludge to force reading of first session */
1261     long time, increment;
1262     char *s = tcString;
1263
1264     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1265     do {
1266         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1267         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1268         if(movenr == -1) return time;    /* last move before new session     */
1269         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1270         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1271         if(!moves) return increment;     /* current session is incremental   */
1272         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1273     } while(movenr >= -1);               /* try again for next session       */
1274
1275     return 0; // no new time quota on this move
1276 }
1277
1278 int
1279 ParseTimeControl (char *tc, float ti, int mps)
1280 {
1281   long tc1;
1282   long tc2;
1283   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1284   int min, sec=0;
1285
1286   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1287   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1288       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1289   if(ti > 0) {
1290
1291     if(mps)
1292       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1293     else
1294       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1295   } else {
1296     if(mps)
1297       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1298     else
1299       snprintf(buf, MSG_SIZ, ":%s", mytc);
1300   }
1301   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1302
1303   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1304     return FALSE;
1305   }
1306
1307   if( *tc == '/' ) {
1308     /* Parse second time control */
1309     tc++;
1310
1311     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1312       return FALSE;
1313     }
1314
1315     if( tc2 == 0 ) {
1316       return FALSE;
1317     }
1318
1319     timeControl_2 = tc2 * 1000;
1320   }
1321   else {
1322     timeControl_2 = 0;
1323   }
1324
1325   if( tc1 == 0 ) {
1326     return FALSE;
1327   }
1328
1329   timeControl = tc1 * 1000;
1330
1331   if (ti >= 0) {
1332     timeIncrement = ti * 1000;  /* convert to ms */
1333     movesPerSession = 0;
1334   } else {
1335     timeIncrement = 0;
1336     movesPerSession = mps;
1337   }
1338   return TRUE;
1339 }
1340
1341 void
1342 InitBackEnd2 ()
1343 {
1344     if (appData.debugMode) {
1345         fprintf(debugFP, "%s\n", programVersion);
1346     }
1347     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1348
1349     set_cont_sequence(appData.wrapContSeq);
1350     if (appData.matchGames > 0) {
1351         appData.matchMode = TRUE;
1352     } else if (appData.matchMode) {
1353         appData.matchGames = 1;
1354     }
1355     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1356         appData.matchGames = appData.sameColorGames;
1357     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1358         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1359         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1360     }
1361     Reset(TRUE, FALSE);
1362     if (appData.noChessProgram || first.protocolVersion == 1) {
1363       InitBackEnd3();
1364     } else {
1365       /* kludge: allow timeout for initial "feature" commands */
1366       FreezeUI();
1367       DisplayMessage("", _("Starting chess program"));
1368       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1369     }
1370 }
1371
1372 int
1373 CalculateIndex (int index, int gameNr)
1374 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1375     int res;
1376     if(index > 0) return index; // fixed nmber
1377     if(index == 0) return 1;
1378     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1379     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1380     return res;
1381 }
1382
1383 int
1384 LoadGameOrPosition (int gameNr)
1385 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1386     if (*appData.loadGameFile != NULLCHAR) {
1387         if (!LoadGameFromFile(appData.loadGameFile,
1388                 CalculateIndex(appData.loadGameIndex, gameNr),
1389                               appData.loadGameFile, FALSE)) {
1390             DisplayFatalError(_("Bad game file"), 0, 1);
1391             return 0;
1392         }
1393     } else if (*appData.loadPositionFile != NULLCHAR) {
1394         if (!LoadPositionFromFile(appData.loadPositionFile,
1395                 CalculateIndex(appData.loadPositionIndex, gameNr),
1396                                   appData.loadPositionFile)) {
1397             DisplayFatalError(_("Bad position file"), 0, 1);
1398             return 0;
1399         }
1400     }
1401     return 1;
1402 }
1403
1404 void
1405 ReserveGame (int gameNr, char resChar)
1406 {
1407     FILE *tf = fopen(appData.tourneyFile, "r+");
1408     char *p, *q, c, buf[MSG_SIZ];
1409     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1410     safeStrCpy(buf, lastMsg, MSG_SIZ);
1411     DisplayMessage(_("Pick new game"), "");
1412     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1413     ParseArgsFromFile(tf);
1414     p = q = appData.results;
1415     if(appData.debugMode) {
1416       char *r = appData.participants;
1417       fprintf(debugFP, "results = '%s'\n", p);
1418       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1419       fprintf(debugFP, "\n");
1420     }
1421     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1422     nextGame = q - p;
1423     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1424     safeStrCpy(q, p, strlen(p) + 2);
1425     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1426     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1427     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1428         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1429         q[nextGame] = '*';
1430     }
1431     fseek(tf, -(strlen(p)+4), SEEK_END);
1432     c = fgetc(tf);
1433     if(c != '"') // depending on DOS or Unix line endings we can be one off
1434          fseek(tf, -(strlen(p)+2), SEEK_END);
1435     else fseek(tf, -(strlen(p)+3), SEEK_END);
1436     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1437     DisplayMessage(buf, "");
1438     free(p); appData.results = q;
1439     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1440        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1441       int round = appData.defaultMatchGames * appData.tourneyType;
1442       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1443          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1444         UnloadEngine(&first);  // next game belongs to other pairing;
1445         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1446     }
1447     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1448 }
1449
1450 void
1451 MatchEvent (int mode)
1452 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1453         int dummy;
1454         if(matchMode) { // already in match mode: switch it off
1455             abortMatch = TRUE;
1456             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1457             return;
1458         }
1459 //      if(gameMode != BeginningOfGame) {
1460 //          DisplayError(_("You can only start a match from the initial position."), 0);
1461 //          return;
1462 //      }
1463         abortMatch = FALSE;
1464         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1465         /* Set up machine vs. machine match */
1466         nextGame = 0;
1467         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1468         if(appData.tourneyFile[0]) {
1469             ReserveGame(-1, 0);
1470             if(nextGame > appData.matchGames) {
1471                 char buf[MSG_SIZ];
1472                 if(strchr(appData.results, '*') == NULL) {
1473                     FILE *f;
1474                     appData.tourneyCycles++;
1475                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1476                         fclose(f);
1477                         NextTourneyGame(-1, &dummy);
1478                         ReserveGame(-1, 0);
1479                         if(nextGame <= appData.matchGames) {
1480                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1481                             matchMode = mode;
1482                             ScheduleDelayedEvent(NextMatchGame, 10000);
1483                             return;
1484                         }
1485                     }
1486                 }
1487                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1488                 DisplayError(buf, 0);
1489                 appData.tourneyFile[0] = 0;
1490                 return;
1491             }
1492         } else
1493         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1494             DisplayFatalError(_("Can't have a match with no chess programs"),
1495                               0, 2);
1496             return;
1497         }
1498         matchMode = mode;
1499         matchGame = roundNr = 1;
1500         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1501         NextMatchGame();
1502 }
1503
1504 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1505
1506 void
1507 InitBackEnd3 P((void))
1508 {
1509     GameMode initialMode;
1510     char buf[MSG_SIZ];
1511     int err, len;
1512
1513     InitChessProgram(&first, startedFromSetupPosition);
1514
1515     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1516         free(programVersion);
1517         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1518         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1519         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1520     }
1521
1522     if (appData.icsActive) {
1523 #ifdef WIN32
1524         /* [DM] Make a console window if needed [HGM] merged ifs */
1525         ConsoleCreate();
1526 #endif
1527         err = establish();
1528         if (err != 0)
1529           {
1530             if (*appData.icsCommPort != NULLCHAR)
1531               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1532                              appData.icsCommPort);
1533             else
1534               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1535                         appData.icsHost, appData.icsPort);
1536
1537             if( (len >= MSG_SIZ) && appData.debugMode )
1538               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1539
1540             DisplayFatalError(buf, err, 1);
1541             return;
1542         }
1543         SetICSMode();
1544         telnetISR =
1545           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1546         fromUserISR =
1547           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1548         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1549             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1550     } else if (appData.noChessProgram) {
1551         SetNCPMode();
1552     } else {
1553         SetGNUMode();
1554     }
1555
1556     if (*appData.cmailGameName != NULLCHAR) {
1557         SetCmailMode();
1558         OpenLoopback(&cmailPR);
1559         cmailISR =
1560           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1561     }
1562
1563     ThawUI();
1564     DisplayMessage("", "");
1565     if (StrCaseCmp(appData.initialMode, "") == 0) {
1566       initialMode = BeginningOfGame;
1567       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1568         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1569         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1570         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1571         ModeHighlight();
1572       }
1573     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1574       initialMode = TwoMachinesPlay;
1575     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1576       initialMode = AnalyzeFile;
1577     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1578       initialMode = AnalyzeMode;
1579     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1580       initialMode = MachinePlaysWhite;
1581     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1582       initialMode = MachinePlaysBlack;
1583     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1584       initialMode = EditGame;
1585     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1586       initialMode = EditPosition;
1587     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1588       initialMode = Training;
1589     } else {
1590       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1591       if( (len >= MSG_SIZ) && appData.debugMode )
1592         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1593
1594       DisplayFatalError(buf, 0, 2);
1595       return;
1596     }
1597
1598     if (appData.matchMode) {
1599         if(appData.tourneyFile[0]) { // start tourney from command line
1600             FILE *f;
1601             if(f = fopen(appData.tourneyFile, "r")) {
1602                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1603                 fclose(f);
1604                 appData.clockMode = TRUE;
1605                 SetGNUMode();
1606             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1607         }
1608         MatchEvent(TRUE);
1609     } else if (*appData.cmailGameName != NULLCHAR) {
1610         /* Set up cmail mode */
1611         ReloadCmailMsgEvent(TRUE);
1612     } else {
1613         /* Set up other modes */
1614         if (initialMode == AnalyzeFile) {
1615           if (*appData.loadGameFile == NULLCHAR) {
1616             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1617             return;
1618           }
1619         }
1620         if (*appData.loadGameFile != NULLCHAR) {
1621             (void) LoadGameFromFile(appData.loadGameFile,
1622                                     appData.loadGameIndex,
1623                                     appData.loadGameFile, TRUE);
1624         } else if (*appData.loadPositionFile != NULLCHAR) {
1625             (void) LoadPositionFromFile(appData.loadPositionFile,
1626                                         appData.loadPositionIndex,
1627                                         appData.loadPositionFile);
1628             /* [HGM] try to make self-starting even after FEN load */
1629             /* to allow automatic setup of fairy variants with wtm */
1630             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1631                 gameMode = BeginningOfGame;
1632                 setboardSpoiledMachineBlack = 1;
1633             }
1634             /* [HGM] loadPos: make that every new game uses the setup */
1635             /* from file as long as we do not switch variant          */
1636             if(!blackPlaysFirst) {
1637                 startedFromPositionFile = TRUE;
1638                 CopyBoard(filePosition, boards[0]);
1639             }
1640         }
1641         if (initialMode == AnalyzeMode) {
1642           if (appData.noChessProgram) {
1643             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1644             return;
1645           }
1646           if (appData.icsActive) {
1647             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1648             return;
1649           }
1650           AnalyzeModeEvent();
1651         } else if (initialMode == AnalyzeFile) {
1652           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1653           ShowThinkingEvent();
1654           AnalyzeFileEvent();
1655           AnalysisPeriodicEvent(1);
1656         } else if (initialMode == MachinePlaysWhite) {
1657           if (appData.noChessProgram) {
1658             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1659                               0, 2);
1660             return;
1661           }
1662           if (appData.icsActive) {
1663             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1664                               0, 2);
1665             return;
1666           }
1667           MachineWhiteEvent();
1668         } else if (initialMode == MachinePlaysBlack) {
1669           if (appData.noChessProgram) {
1670             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1671                               0, 2);
1672             return;
1673           }
1674           if (appData.icsActive) {
1675             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1676                               0, 2);
1677             return;
1678           }
1679           MachineBlackEvent();
1680         } else if (initialMode == TwoMachinesPlay) {
1681           if (appData.noChessProgram) {
1682             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1683                               0, 2);
1684             return;
1685           }
1686           if (appData.icsActive) {
1687             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1688                               0, 2);
1689             return;
1690           }
1691           TwoMachinesEvent();
1692         } else if (initialMode == EditGame) {
1693           EditGameEvent();
1694         } else if (initialMode == EditPosition) {
1695           EditPositionEvent();
1696         } else if (initialMode == Training) {
1697           if (*appData.loadGameFile == NULLCHAR) {
1698             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1699             return;
1700           }
1701           TrainingEvent();
1702         }
1703     }
1704 }
1705
1706 void
1707 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1708 {
1709     DisplayBook(current+1);
1710
1711     MoveHistorySet( movelist, first, last, current, pvInfoList );
1712
1713     EvalGraphSet( first, last, current, pvInfoList );
1714
1715     MakeEngineOutputTitle();
1716 }
1717
1718 /*
1719  * Establish will establish a contact to a remote host.port.
1720  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1721  *  used to talk to the host.
1722  * Returns 0 if okay, error code if not.
1723  */
1724 int
1725 establish ()
1726 {
1727     char buf[MSG_SIZ];
1728
1729     if (*appData.icsCommPort != NULLCHAR) {
1730         /* Talk to the host through a serial comm port */
1731         return OpenCommPort(appData.icsCommPort, &icsPR);
1732
1733     } else if (*appData.gateway != NULLCHAR) {
1734         if (*appData.remoteShell == NULLCHAR) {
1735             /* Use the rcmd protocol to run telnet program on a gateway host */
1736             snprintf(buf, sizeof(buf), "%s %s %s",
1737                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1738             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1739
1740         } else {
1741             /* Use the rsh program to run telnet program on a gateway host */
1742             if (*appData.remoteUser == NULLCHAR) {
1743                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1744                         appData.gateway, appData.telnetProgram,
1745                         appData.icsHost, appData.icsPort);
1746             } else {
1747                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1748                         appData.remoteShell, appData.gateway,
1749                         appData.remoteUser, appData.telnetProgram,
1750                         appData.icsHost, appData.icsPort);
1751             }
1752             return StartChildProcess(buf, "", &icsPR);
1753
1754         }
1755     } else if (appData.useTelnet) {
1756         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1757
1758     } else {
1759         /* TCP socket interface differs somewhat between
1760            Unix and NT; handle details in the front end.
1761            */
1762         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1763     }
1764 }
1765
1766 void
1767 EscapeExpand (char *p, char *q)
1768 {       // [HGM] initstring: routine to shape up string arguments
1769         while(*p++ = *q++) if(p[-1] == '\\')
1770             switch(*q++) {
1771                 case 'n': p[-1] = '\n'; break;
1772                 case 'r': p[-1] = '\r'; break;
1773                 case 't': p[-1] = '\t'; break;
1774                 case '\\': p[-1] = '\\'; break;
1775                 case 0: *p = 0; return;
1776                 default: p[-1] = q[-1]; break;
1777             }
1778 }
1779
1780 void
1781 show_bytes (FILE *fp, char *buf, int count)
1782 {
1783     while (count--) {
1784         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1785             fprintf(fp, "\\%03o", *buf & 0xff);
1786         } else {
1787             putc(*buf, fp);
1788         }
1789         buf++;
1790     }
1791     fflush(fp);
1792 }
1793
1794 /* Returns an errno value */
1795 int
1796 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1797 {
1798     char buf[8192], *p, *q, *buflim;
1799     int left, newcount, outcount;
1800
1801     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1802         *appData.gateway != NULLCHAR) {
1803         if (appData.debugMode) {
1804             fprintf(debugFP, ">ICS: ");
1805             show_bytes(debugFP, message, count);
1806             fprintf(debugFP, "\n");
1807         }
1808         return OutputToProcess(pr, message, count, outError);
1809     }
1810
1811     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1812     p = message;
1813     q = buf;
1814     left = count;
1815     newcount = 0;
1816     while (left) {
1817         if (q >= buflim) {
1818             if (appData.debugMode) {
1819                 fprintf(debugFP, ">ICS: ");
1820                 show_bytes(debugFP, buf, newcount);
1821                 fprintf(debugFP, "\n");
1822             }
1823             outcount = OutputToProcess(pr, buf, newcount, outError);
1824             if (outcount < newcount) return -1; /* to be sure */
1825             q = buf;
1826             newcount = 0;
1827         }
1828         if (*p == '\n') {
1829             *q++ = '\r';
1830             newcount++;
1831         } else if (((unsigned char) *p) == TN_IAC) {
1832             *q++ = (char) TN_IAC;
1833             newcount ++;
1834         }
1835         *q++ = *p++;
1836         newcount++;
1837         left--;
1838     }
1839     if (appData.debugMode) {
1840         fprintf(debugFP, ">ICS: ");
1841         show_bytes(debugFP, buf, newcount);
1842         fprintf(debugFP, "\n");
1843     }
1844     outcount = OutputToProcess(pr, buf, newcount, outError);
1845     if (outcount < newcount) return -1; /* to be sure */
1846     return count;
1847 }
1848
1849 void
1850 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1851 {
1852     int outError, outCount;
1853     static int gotEof = 0;
1854     static FILE *ini;
1855
1856     /* Pass data read from player on to ICS */
1857     if (count > 0) {
1858         gotEof = 0;
1859         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1860         if (outCount < count) {
1861             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1862         }
1863         if(have_sent_ICS_logon == 2) {
1864           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1865             fprintf(ini, "%s", message);
1866             have_sent_ICS_logon = 3;
1867           } else
1868             have_sent_ICS_logon = 1;
1869         } else if(have_sent_ICS_logon == 3) {
1870             fprintf(ini, "%s", message);
1871             fclose(ini);
1872           have_sent_ICS_logon = 1;
1873         }
1874     } else if (count < 0) {
1875         RemoveInputSource(isr);
1876         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1877     } else if (gotEof++ > 0) {
1878         RemoveInputSource(isr);
1879         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1880     }
1881 }
1882
1883 void
1884 KeepAlive ()
1885 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1886     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1887     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1888     SendToICS("date\n");
1889     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1890 }
1891
1892 /* added routine for printf style output to ics */
1893 void
1894 ics_printf (char *format, ...)
1895 {
1896     char buffer[MSG_SIZ];
1897     va_list args;
1898
1899     va_start(args, format);
1900     vsnprintf(buffer, sizeof(buffer), format, args);
1901     buffer[sizeof(buffer)-1] = '\0';
1902     SendToICS(buffer);
1903     va_end(args);
1904 }
1905
1906 void
1907 SendToICS (char *s)
1908 {
1909     int count, outCount, outError;
1910
1911     if (icsPR == NoProc) return;
1912
1913     count = strlen(s);
1914     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1915     if (outCount < count) {
1916         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1917     }
1918 }
1919
1920 /* This is used for sending logon scripts to the ICS. Sending
1921    without a delay causes problems when using timestamp on ICC
1922    (at least on my machine). */
1923 void
1924 SendToICSDelayed (char *s, long msdelay)
1925 {
1926     int count, outCount, outError;
1927
1928     if (icsPR == NoProc) return;
1929
1930     count = strlen(s);
1931     if (appData.debugMode) {
1932         fprintf(debugFP, ">ICS: ");
1933         show_bytes(debugFP, s, count);
1934         fprintf(debugFP, "\n");
1935     }
1936     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1937                                       msdelay);
1938     if (outCount < count) {
1939         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1940     }
1941 }
1942
1943
1944 /* Remove all highlighting escape sequences in s
1945    Also deletes any suffix starting with '('
1946    */
1947 char *
1948 StripHighlightAndTitle (char *s)
1949 {
1950     static char retbuf[MSG_SIZ];
1951     char *p = retbuf;
1952
1953     while (*s != NULLCHAR) {
1954         while (*s == '\033') {
1955             while (*s != NULLCHAR && !isalpha(*s)) s++;
1956             if (*s != NULLCHAR) s++;
1957         }
1958         while (*s != NULLCHAR && *s != '\033') {
1959             if (*s == '(' || *s == '[') {
1960                 *p = NULLCHAR;
1961                 return retbuf;
1962             }
1963             *p++ = *s++;
1964         }
1965     }
1966     *p = NULLCHAR;
1967     return retbuf;
1968 }
1969
1970 /* Remove all highlighting escape sequences in s */
1971 char *
1972 StripHighlight (char *s)
1973 {
1974     static char retbuf[MSG_SIZ];
1975     char *p = retbuf;
1976
1977     while (*s != NULLCHAR) {
1978         while (*s == '\033') {
1979             while (*s != NULLCHAR && !isalpha(*s)) s++;
1980             if (*s != NULLCHAR) s++;
1981         }
1982         while (*s != NULLCHAR && *s != '\033') {
1983             *p++ = *s++;
1984         }
1985     }
1986     *p = NULLCHAR;
1987     return retbuf;
1988 }
1989
1990 char *variantNames[] = VARIANT_NAMES;
1991 char *
1992 VariantName (VariantClass v)
1993 {
1994     return variantNames[v];
1995 }
1996
1997
1998 /* Identify a variant from the strings the chess servers use or the
1999    PGN Variant tag names we use. */
2000 VariantClass
2001 StringToVariant (char *e)
2002 {
2003     char *p;
2004     int wnum = -1;
2005     VariantClass v = VariantNormal;
2006     int i, found = FALSE;
2007     char buf[MSG_SIZ];
2008     int len;
2009
2010     if (!e) return v;
2011
2012     /* [HGM] skip over optional board-size prefixes */
2013     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2014         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2015         while( *e++ != '_');
2016     }
2017
2018     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2019         v = VariantNormal;
2020         found = TRUE;
2021     } else
2022     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2023       if (StrCaseStr(e, variantNames[i])) {
2024         v = (VariantClass) i;
2025         found = TRUE;
2026         break;
2027       }
2028     }
2029
2030     if (!found) {
2031       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2032           || StrCaseStr(e, "wild/fr")
2033           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2034         v = VariantFischeRandom;
2035       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2036                  (i = 1, p = StrCaseStr(e, "w"))) {
2037         p += i;
2038         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2039         if (isdigit(*p)) {
2040           wnum = atoi(p);
2041         } else {
2042           wnum = -1;
2043         }
2044         switch (wnum) {
2045         case 0: /* FICS only, actually */
2046         case 1:
2047           /* Castling legal even if K starts on d-file */
2048           v = VariantWildCastle;
2049           break;
2050         case 2:
2051         case 3:
2052         case 4:
2053           /* Castling illegal even if K & R happen to start in
2054              normal positions. */
2055           v = VariantNoCastle;
2056           break;
2057         case 5:
2058         case 7:
2059         case 8:
2060         case 10:
2061         case 11:
2062         case 12:
2063         case 13:
2064         case 14:
2065         case 15:
2066         case 18:
2067         case 19:
2068           /* Castling legal iff K & R start in normal positions */
2069           v = VariantNormal;
2070           break;
2071         case 6:
2072         case 20:
2073         case 21:
2074           /* Special wilds for position setup; unclear what to do here */
2075           v = VariantLoadable;
2076           break;
2077         case 9:
2078           /* Bizarre ICC game */
2079           v = VariantTwoKings;
2080           break;
2081         case 16:
2082           v = VariantKriegspiel;
2083           break;
2084         case 17:
2085           v = VariantLosers;
2086           break;
2087         case 22:
2088           v = VariantFischeRandom;
2089           break;
2090         case 23:
2091           v = VariantCrazyhouse;
2092           break;
2093         case 24:
2094           v = VariantBughouse;
2095           break;
2096         case 25:
2097           v = Variant3Check;
2098           break;
2099         case 26:
2100           /* Not quite the same as FICS suicide! */
2101           v = VariantGiveaway;
2102           break;
2103         case 27:
2104           v = VariantAtomic;
2105           break;
2106         case 28:
2107           v = VariantShatranj;
2108           break;
2109
2110         /* Temporary names for future ICC types.  The name *will* change in
2111            the next xboard/WinBoard release after ICC defines it. */
2112         case 29:
2113           v = Variant29;
2114           break;
2115         case 30:
2116           v = Variant30;
2117           break;
2118         case 31:
2119           v = Variant31;
2120           break;
2121         case 32:
2122           v = Variant32;
2123           break;
2124         case 33:
2125           v = Variant33;
2126           break;
2127         case 34:
2128           v = Variant34;
2129           break;
2130         case 35:
2131           v = Variant35;
2132           break;
2133         case 36:
2134           v = Variant36;
2135           break;
2136         case 37:
2137           v = VariantShogi;
2138           break;
2139         case 38:
2140           v = VariantXiangqi;
2141           break;
2142         case 39:
2143           v = VariantCourier;
2144           break;
2145         case 40:
2146           v = VariantGothic;
2147           break;
2148         case 41:
2149           v = VariantCapablanca;
2150           break;
2151         case 42:
2152           v = VariantKnightmate;
2153           break;
2154         case 43:
2155           v = VariantFairy;
2156           break;
2157         case 44:
2158           v = VariantCylinder;
2159           break;
2160         case 45:
2161           v = VariantFalcon;
2162           break;
2163         case 46:
2164           v = VariantCapaRandom;
2165           break;
2166         case 47:
2167           v = VariantBerolina;
2168           break;
2169         case 48:
2170           v = VariantJanus;
2171           break;
2172         case 49:
2173           v = VariantSuper;
2174           break;
2175         case 50:
2176           v = VariantGreat;
2177           break;
2178         case -1:
2179           /* Found "wild" or "w" in the string but no number;
2180              must assume it's normal chess. */
2181           v = VariantNormal;
2182           break;
2183         default:
2184           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2185           if( (len >= MSG_SIZ) && appData.debugMode )
2186             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2187
2188           DisplayError(buf, 0);
2189           v = VariantUnknown;
2190           break;
2191         }
2192       }
2193     }
2194     if (appData.debugMode) {
2195       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2196               e, wnum, VariantName(v));
2197     }
2198     return v;
2199 }
2200
2201 static int leftover_start = 0, leftover_len = 0;
2202 char star_match[STAR_MATCH_N][MSG_SIZ];
2203
2204 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2205    advance *index beyond it, and set leftover_start to the new value of
2206    *index; else return FALSE.  If pattern contains the character '*', it
2207    matches any sequence of characters not containing '\r', '\n', or the
2208    character following the '*' (if any), and the matched sequence(s) are
2209    copied into star_match.
2210    */
2211 int
2212 looking_at ( char *buf, int *index, char *pattern)
2213 {
2214     char *bufp = &buf[*index], *patternp = pattern;
2215     int star_count = 0;
2216     char *matchp = star_match[0];
2217
2218     for (;;) {
2219         if (*patternp == NULLCHAR) {
2220             *index = leftover_start = bufp - buf;
2221             *matchp = NULLCHAR;
2222             return TRUE;
2223         }
2224         if (*bufp == NULLCHAR) return FALSE;
2225         if (*patternp == '*') {
2226             if (*bufp == *(patternp + 1)) {
2227                 *matchp = NULLCHAR;
2228                 matchp = star_match[++star_count];
2229                 patternp += 2;
2230                 bufp++;
2231                 continue;
2232             } else if (*bufp == '\n' || *bufp == '\r') {
2233                 patternp++;
2234                 if (*patternp == NULLCHAR)
2235                   continue;
2236                 else
2237                   return FALSE;
2238             } else {
2239                 *matchp++ = *bufp++;
2240                 continue;
2241             }
2242         }
2243         if (*patternp != *bufp) return FALSE;
2244         patternp++;
2245         bufp++;
2246     }
2247 }
2248
2249 void
2250 SendToPlayer (char *data, int length)
2251 {
2252     int error, outCount;
2253     outCount = OutputToProcess(NoProc, data, length, &error);
2254     if (outCount < length) {
2255         DisplayFatalError(_("Error writing to display"), error, 1);
2256     }
2257 }
2258
2259 void
2260 PackHolding (char packed[], char *holding)
2261 {
2262     char *p = holding;
2263     char *q = packed;
2264     int runlength = 0;
2265     int curr = 9999;
2266     do {
2267         if (*p == curr) {
2268             runlength++;
2269         } else {
2270             switch (runlength) {
2271               case 0:
2272                 break;
2273               case 1:
2274                 *q++ = curr;
2275                 break;
2276               case 2:
2277                 *q++ = curr;
2278                 *q++ = curr;
2279                 break;
2280               default:
2281                 sprintf(q, "%d", runlength);
2282                 while (*q) q++;
2283                 *q++ = curr;
2284                 break;
2285             }
2286             runlength = 1;
2287             curr = *p;
2288         }
2289     } while (*p++);
2290     *q = NULLCHAR;
2291 }
2292
2293 /* Telnet protocol requests from the front end */
2294 void
2295 TelnetRequest (unsigned char ddww, unsigned char option)
2296 {
2297     unsigned char msg[3];
2298     int outCount, outError;
2299
2300     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2301
2302     if (appData.debugMode) {
2303         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2304         switch (ddww) {
2305           case TN_DO:
2306             ddwwStr = "DO";
2307             break;
2308           case TN_DONT:
2309             ddwwStr = "DONT";
2310             break;
2311           case TN_WILL:
2312             ddwwStr = "WILL";
2313             break;
2314           case TN_WONT:
2315             ddwwStr = "WONT";
2316             break;
2317           default:
2318             ddwwStr = buf1;
2319             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2320             break;
2321         }
2322         switch (option) {
2323           case TN_ECHO:
2324             optionStr = "ECHO";
2325             break;
2326           default:
2327             optionStr = buf2;
2328             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2329             break;
2330         }
2331         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2332     }
2333     msg[0] = TN_IAC;
2334     msg[1] = ddww;
2335     msg[2] = option;
2336     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2337     if (outCount < 3) {
2338         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2339     }
2340 }
2341
2342 void
2343 DoEcho ()
2344 {
2345     if (!appData.icsActive) return;
2346     TelnetRequest(TN_DO, TN_ECHO);
2347 }
2348
2349 void
2350 DontEcho ()
2351 {
2352     if (!appData.icsActive) return;
2353     TelnetRequest(TN_DONT, TN_ECHO);
2354 }
2355
2356 void
2357 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2358 {
2359     /* put the holdings sent to us by the server on the board holdings area */
2360     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2361     char p;
2362     ChessSquare piece;
2363
2364     if(gameInfo.holdingsWidth < 2)  return;
2365     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2366         return; // prevent overwriting by pre-board holdings
2367
2368     if( (int)lowestPiece >= BlackPawn ) {
2369         holdingsColumn = 0;
2370         countsColumn = 1;
2371         holdingsStartRow = BOARD_HEIGHT-1;
2372         direction = -1;
2373     } else {
2374         holdingsColumn = BOARD_WIDTH-1;
2375         countsColumn = BOARD_WIDTH-2;
2376         holdingsStartRow = 0;
2377         direction = 1;
2378     }
2379
2380     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2381         board[i][holdingsColumn] = EmptySquare;
2382         board[i][countsColumn]   = (ChessSquare) 0;
2383     }
2384     while( (p=*holdings++) != NULLCHAR ) {
2385         piece = CharToPiece( ToUpper(p) );
2386         if(piece == EmptySquare) continue;
2387         /*j = (int) piece - (int) WhitePawn;*/
2388         j = PieceToNumber(piece);
2389         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2390         if(j < 0) continue;               /* should not happen */
2391         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2392         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2393         board[holdingsStartRow+j*direction][countsColumn]++;
2394     }
2395 }
2396
2397
2398 void
2399 VariantSwitch (Board board, VariantClass newVariant)
2400 {
2401    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2402    static Board oldBoard;
2403
2404    startedFromPositionFile = FALSE;
2405    if(gameInfo.variant == newVariant) return;
2406
2407    /* [HGM] This routine is called each time an assignment is made to
2408     * gameInfo.variant during a game, to make sure the board sizes
2409     * are set to match the new variant. If that means adding or deleting
2410     * holdings, we shift the playing board accordingly
2411     * This kludge is needed because in ICS observe mode, we get boards
2412     * of an ongoing game without knowing the variant, and learn about the
2413     * latter only later. This can be because of the move list we requested,
2414     * in which case the game history is refilled from the beginning anyway,
2415     * but also when receiving holdings of a crazyhouse game. In the latter
2416     * case we want to add those holdings to the already received position.
2417     */
2418
2419
2420    if (appData.debugMode) {
2421      fprintf(debugFP, "Switch board from %s to %s\n",
2422              VariantName(gameInfo.variant), VariantName(newVariant));
2423      setbuf(debugFP, NULL);
2424    }
2425    shuffleOpenings = 0;       /* [HGM] shuffle */
2426    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2427    switch(newVariant)
2428      {
2429      case VariantShogi:
2430        newWidth = 9;  newHeight = 9;
2431        gameInfo.holdingsSize = 7;
2432      case VariantBughouse:
2433      case VariantCrazyhouse:
2434        newHoldingsWidth = 2; break;
2435      case VariantGreat:
2436        newWidth = 10;
2437      case VariantSuper:
2438        newHoldingsWidth = 2;
2439        gameInfo.holdingsSize = 8;
2440        break;
2441      case VariantGothic:
2442      case VariantCapablanca:
2443      case VariantCapaRandom:
2444        newWidth = 10;
2445      default:
2446        newHoldingsWidth = gameInfo.holdingsSize = 0;
2447      };
2448
2449    if(newWidth  != gameInfo.boardWidth  ||
2450       newHeight != gameInfo.boardHeight ||
2451       newHoldingsWidth != gameInfo.holdingsWidth ) {
2452
2453      /* shift position to new playing area, if needed */
2454      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2455        for(i=0; i<BOARD_HEIGHT; i++)
2456          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2457            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2458              board[i][j];
2459        for(i=0; i<newHeight; i++) {
2460          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2461          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2462        }
2463      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2464        for(i=0; i<BOARD_HEIGHT; i++)
2465          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2466            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2467              board[i][j];
2468      }
2469      board[HOLDINGS_SET] = 0;
2470      gameInfo.boardWidth  = newWidth;
2471      gameInfo.boardHeight = newHeight;
2472      gameInfo.holdingsWidth = newHoldingsWidth;
2473      gameInfo.variant = newVariant;
2474      InitDrawingSizes(-2, 0);
2475    } else gameInfo.variant = newVariant;
2476    CopyBoard(oldBoard, board);   // remember correctly formatted board
2477      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2478    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2479 }
2480
2481 static int loggedOn = FALSE;
2482
2483 /*-- Game start info cache: --*/
2484 int gs_gamenum;
2485 char gs_kind[MSG_SIZ];
2486 static char player1Name[128] = "";
2487 static char player2Name[128] = "";
2488 static char cont_seq[] = "\n\\   ";
2489 static int player1Rating = -1;
2490 static int player2Rating = -1;
2491 /*----------------------------*/
2492
2493 ColorClass curColor = ColorNormal;
2494 int suppressKibitz = 0;
2495
2496 // [HGM] seekgraph
2497 Boolean soughtPending = FALSE;
2498 Boolean seekGraphUp;
2499 #define MAX_SEEK_ADS 200
2500 #define SQUARE 0x80
2501 char *seekAdList[MAX_SEEK_ADS];
2502 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2503 float tcList[MAX_SEEK_ADS];
2504 char colorList[MAX_SEEK_ADS];
2505 int nrOfSeekAds = 0;
2506 int minRating = 1010, maxRating = 2800;
2507 int hMargin = 10, vMargin = 20, h, w;
2508 extern int squareSize, lineGap;
2509
2510 void
2511 PlotSeekAd (int i)
2512 {
2513         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2514         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2515         if(r < minRating+100 && r >=0 ) r = minRating+100;
2516         if(r > maxRating) r = maxRating;
2517         if(tc < 1.f) tc = 1.f;
2518         if(tc > 95.f) tc = 95.f;
2519         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2520         y = ((double)r - minRating)/(maxRating - minRating)
2521             * (h-vMargin-squareSize/8-1) + vMargin;
2522         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2523         if(strstr(seekAdList[i], " u ")) color = 1;
2524         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2525            !strstr(seekAdList[i], "bullet") &&
2526            !strstr(seekAdList[i], "blitz") &&
2527            !strstr(seekAdList[i], "standard") ) color = 2;
2528         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2529         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2530 }
2531
2532 void
2533 PlotSingleSeekAd (int i)
2534 {
2535         PlotSeekAd(i);
2536 }
2537
2538 void
2539 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2540 {
2541         char buf[MSG_SIZ], *ext = "";
2542         VariantClass v = StringToVariant(type);
2543         if(strstr(type, "wild")) {
2544             ext = type + 4; // append wild number
2545             if(v == VariantFischeRandom) type = "chess960"; else
2546             if(v == VariantLoadable) type = "setup"; else
2547             type = VariantName(v);
2548         }
2549         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2550         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2551             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2552             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2553             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2554             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2555             seekNrList[nrOfSeekAds] = nr;
2556             zList[nrOfSeekAds] = 0;
2557             seekAdList[nrOfSeekAds++] = StrSave(buf);
2558             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2559         }
2560 }
2561
2562 void
2563 EraseSeekDot (int i)
2564 {
2565     int x = xList[i], y = yList[i], d=squareSize/4, k;
2566     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2567     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2568     // now replot every dot that overlapped
2569     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2570         int xx = xList[k], yy = yList[k];
2571         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2572             DrawSeekDot(xx, yy, colorList[k]);
2573     }
2574 }
2575
2576 void
2577 RemoveSeekAd (int nr)
2578 {
2579         int i;
2580         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2581             EraseSeekDot(i);
2582             if(seekAdList[i]) free(seekAdList[i]);
2583             seekAdList[i] = seekAdList[--nrOfSeekAds];
2584             seekNrList[i] = seekNrList[nrOfSeekAds];
2585             ratingList[i] = ratingList[nrOfSeekAds];
2586             colorList[i]  = colorList[nrOfSeekAds];
2587             tcList[i] = tcList[nrOfSeekAds];
2588             xList[i]  = xList[nrOfSeekAds];
2589             yList[i]  = yList[nrOfSeekAds];
2590             zList[i]  = zList[nrOfSeekAds];
2591             seekAdList[nrOfSeekAds] = NULL;
2592             break;
2593         }
2594 }
2595
2596 Boolean
2597 MatchSoughtLine (char *line)
2598 {
2599     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2600     int nr, base, inc, u=0; char dummy;
2601
2602     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2603        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2604        (u=1) &&
2605        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2606         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2607         // match: compact and save the line
2608         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2609         return TRUE;
2610     }
2611     return FALSE;
2612 }
2613
2614 int
2615 DrawSeekGraph ()
2616 {
2617     int i;
2618     if(!seekGraphUp) return FALSE;
2619     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2620     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2621
2622     DrawSeekBackground(0, 0, w, h);
2623     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2624     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2625     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2626         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2627         yy = h-1-yy;
2628         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2629         if(i%500 == 0) {
2630             char buf[MSG_SIZ];
2631             snprintf(buf, MSG_SIZ, "%d", i);
2632             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2633         }
2634     }
2635     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2636     for(i=1; i<100; i+=(i<10?1:5)) {
2637         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2638         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2639         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2640             char buf[MSG_SIZ];
2641             snprintf(buf, MSG_SIZ, "%d", i);
2642             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2643         }
2644     }
2645     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2646     return TRUE;
2647 }
2648
2649 int
2650 SeekGraphClick (ClickType click, int x, int y, int moving)
2651 {
2652     static int lastDown = 0, displayed = 0, lastSecond;
2653     if(y < 0) return FALSE;
2654     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2655         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2656         if(!seekGraphUp) return FALSE;
2657         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2658         DrawPosition(TRUE, NULL);
2659         return TRUE;
2660     }
2661     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2662         if(click == Release || moving) return FALSE;
2663         nrOfSeekAds = 0;
2664         soughtPending = TRUE;
2665         SendToICS(ics_prefix);
2666         SendToICS("sought\n"); // should this be "sought all"?
2667     } else { // issue challenge based on clicked ad
2668         int dist = 10000; int i, closest = 0, second = 0;
2669         for(i=0; i<nrOfSeekAds; i++) {
2670             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2671             if(d < dist) { dist = d; closest = i; }
2672             second += (d - zList[i] < 120); // count in-range ads
2673             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2674         }
2675         if(dist < 120) {
2676             char buf[MSG_SIZ];
2677             second = (second > 1);
2678             if(displayed != closest || second != lastSecond) {
2679                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2680                 lastSecond = second; displayed = closest;
2681             }
2682             if(click == Press) {
2683                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2684                 lastDown = closest;
2685                 return TRUE;
2686             } // on press 'hit', only show info
2687             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2688             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2689             SendToICS(ics_prefix);
2690             SendToICS(buf);
2691             return TRUE; // let incoming board of started game pop down the graph
2692         } else if(click == Release) { // release 'miss' is ignored
2693             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2694             if(moving == 2) { // right up-click
2695                 nrOfSeekAds = 0; // refresh graph
2696                 soughtPending = TRUE;
2697                 SendToICS(ics_prefix);
2698                 SendToICS("sought\n"); // should this be "sought all"?
2699             }
2700             return TRUE;
2701         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2702         // press miss or release hit 'pop down' seek graph
2703         seekGraphUp = FALSE;
2704         DrawPosition(TRUE, NULL);
2705     }
2706     return TRUE;
2707 }
2708
2709 void
2710 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2711 {
2712 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2713 #define STARTED_NONE 0
2714 #define STARTED_MOVES 1
2715 #define STARTED_BOARD 2
2716 #define STARTED_OBSERVE 3
2717 #define STARTED_HOLDINGS 4
2718 #define STARTED_CHATTER 5
2719 #define STARTED_COMMENT 6
2720 #define STARTED_MOVES_NOHIDE 7
2721
2722     static int started = STARTED_NONE;
2723     static char parse[20000];
2724     static int parse_pos = 0;
2725     static char buf[BUF_SIZE + 1];
2726     static int firstTime = TRUE, intfSet = FALSE;
2727     static ColorClass prevColor = ColorNormal;
2728     static int savingComment = FALSE;
2729     static int cmatch = 0; // continuation sequence match
2730     char *bp;
2731     char str[MSG_SIZ];
2732     int i, oldi;
2733     int buf_len;
2734     int next_out;
2735     int tkind;
2736     int backup;    /* [DM] For zippy color lines */
2737     char *p;
2738     char talker[MSG_SIZ]; // [HGM] chat
2739     int channel;
2740
2741     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2742
2743     if (appData.debugMode) {
2744       if (!error) {
2745         fprintf(debugFP, "<ICS: ");
2746         show_bytes(debugFP, data, count);
2747         fprintf(debugFP, "\n");
2748       }
2749     }
2750
2751     if (appData.debugMode) { int f = forwardMostMove;
2752         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2753                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2754                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2755     }
2756     if (count > 0) {
2757         /* If last read ended with a partial line that we couldn't parse,
2758            prepend it to the new read and try again. */
2759         if (leftover_len > 0) {
2760             for (i=0; i<leftover_len; i++)
2761               buf[i] = buf[leftover_start + i];
2762         }
2763
2764     /* copy new characters into the buffer */
2765     bp = buf + leftover_len;
2766     buf_len=leftover_len;
2767     for (i=0; i<count; i++)
2768     {
2769         // ignore these
2770         if (data[i] == '\r')
2771             continue;
2772
2773         // join lines split by ICS?
2774         if (!appData.noJoin)
2775         {
2776             /*
2777                 Joining just consists of finding matches against the
2778                 continuation sequence, and discarding that sequence
2779                 if found instead of copying it.  So, until a match
2780                 fails, there's nothing to do since it might be the
2781                 complete sequence, and thus, something we don't want
2782                 copied.
2783             */
2784             if (data[i] == cont_seq[cmatch])
2785             {
2786                 cmatch++;
2787                 if (cmatch == strlen(cont_seq))
2788                 {
2789                     cmatch = 0; // complete match.  just reset the counter
2790
2791                     /*
2792                         it's possible for the ICS to not include the space
2793                         at the end of the last word, making our [correct]
2794                         join operation fuse two separate words.  the server
2795                         does this when the space occurs at the width setting.
2796                     */
2797                     if (!buf_len || buf[buf_len-1] != ' ')
2798                     {
2799                         *bp++ = ' ';
2800                         buf_len++;
2801                     }
2802                 }
2803                 continue;
2804             }
2805             else if (cmatch)
2806             {
2807                 /*
2808                     match failed, so we have to copy what matched before
2809                     falling through and copying this character.  In reality,
2810                     this will only ever be just the newline character, but
2811                     it doesn't hurt to be precise.
2812                 */
2813                 strncpy(bp, cont_seq, cmatch);
2814                 bp += cmatch;
2815                 buf_len += cmatch;
2816                 cmatch = 0;
2817             }
2818         }
2819
2820         // copy this char
2821         *bp++ = data[i];
2822         buf_len++;
2823     }
2824
2825         buf[buf_len] = NULLCHAR;
2826 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2827         next_out = 0;
2828         leftover_start = 0;
2829
2830         i = 0;
2831         while (i < buf_len) {
2832             /* Deal with part of the TELNET option negotiation
2833                protocol.  We refuse to do anything beyond the
2834                defaults, except that we allow the WILL ECHO option,
2835                which ICS uses to turn off password echoing when we are
2836                directly connected to it.  We reject this option
2837                if localLineEditing mode is on (always on in xboard)
2838                and we are talking to port 23, which might be a real
2839                telnet server that will try to keep WILL ECHO on permanently.
2840              */
2841             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2842                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2843                 unsigned char option;
2844                 oldi = i;
2845                 switch ((unsigned char) buf[++i]) {
2846                   case TN_WILL:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<WILL ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       case TN_ECHO:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "ECHO ");
2853                         /* Reply only if this is a change, according
2854                            to the protocol rules. */
2855                         if (remoteEchoOption) break;
2856                         if (appData.localLineEditing &&
2857                             atoi(appData.icsPort) == TN_PORT) {
2858                             TelnetRequest(TN_DONT, TN_ECHO);
2859                         } else {
2860                             EchoOff();
2861                             TelnetRequest(TN_DO, TN_ECHO);
2862                             remoteEchoOption = TRUE;
2863                         }
2864                         break;
2865                       default:
2866                         if (appData.debugMode)
2867                           fprintf(debugFP, "%d ", option);
2868                         /* Whatever this is, we don't want it. */
2869                         TelnetRequest(TN_DONT, option);
2870                         break;
2871                     }
2872                     break;
2873                   case TN_WONT:
2874                     if (appData.debugMode)
2875                       fprintf(debugFP, "\n<WONT ");
2876                     switch (option = (unsigned char) buf[++i]) {
2877                       case TN_ECHO:
2878                         if (appData.debugMode)
2879                           fprintf(debugFP, "ECHO ");
2880                         /* Reply only if this is a change, according
2881                            to the protocol rules. */
2882                         if (!remoteEchoOption) break;
2883                         EchoOn();
2884                         TelnetRequest(TN_DONT, TN_ECHO);
2885                         remoteEchoOption = FALSE;
2886                         break;
2887                       default:
2888                         if (appData.debugMode)
2889                           fprintf(debugFP, "%d ", (unsigned char) option);
2890                         /* Whatever this is, it must already be turned
2891                            off, because we never agree to turn on
2892                            anything non-default, so according to the
2893                            protocol rules, we don't reply. */
2894                         break;
2895                     }
2896                     break;
2897                   case TN_DO:
2898                     if (appData.debugMode)
2899                       fprintf(debugFP, "\n<DO ");
2900                     switch (option = (unsigned char) buf[++i]) {
2901                       default:
2902                         /* Whatever this is, we refuse to do it. */
2903                         if (appData.debugMode)
2904                           fprintf(debugFP, "%d ", option);
2905                         TelnetRequest(TN_WONT, option);
2906                         break;
2907                     }
2908                     break;
2909                   case TN_DONT:
2910                     if (appData.debugMode)
2911                       fprintf(debugFP, "\n<DONT ");
2912                     switch (option = (unsigned char) buf[++i]) {
2913                       default:
2914                         if (appData.debugMode)
2915                           fprintf(debugFP, "%d ", option);
2916                         /* Whatever this is, we are already not doing
2917                            it, because we never agree to do anything
2918                            non-default, so according to the protocol
2919                            rules, we don't reply. */
2920                         break;
2921                     }
2922                     break;
2923                   case TN_IAC:
2924                     if (appData.debugMode)
2925                       fprintf(debugFP, "\n<IAC ");
2926                     /* Doubled IAC; pass it through */
2927                     i--;
2928                     break;
2929                   default:
2930                     if (appData.debugMode)
2931                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2932                     /* Drop all other telnet commands on the floor */
2933                     break;
2934                 }
2935                 if (oldi > next_out)
2936                   SendToPlayer(&buf[next_out], oldi - next_out);
2937                 if (++i > next_out)
2938                   next_out = i;
2939                 continue;
2940             }
2941
2942             /* OK, this at least will *usually* work */
2943             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2944                 loggedOn = TRUE;
2945             }
2946
2947             if (loggedOn && !intfSet) {
2948                 if (ics_type == ICS_ICC) {
2949                   snprintf(str, MSG_SIZ,
2950                           "/set-quietly interface %s\n/set-quietly style 12\n",
2951                           programVersion);
2952                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2953                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2954                 } else if (ics_type == ICS_CHESSNET) {
2955                   snprintf(str, MSG_SIZ, "/style 12\n");
2956                 } else {
2957                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2958                   strcat(str, programVersion);
2959                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2960                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2961                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2962 #ifdef WIN32
2963                   strcat(str, "$iset nohighlight 1\n");
2964 #endif
2965                   strcat(str, "$iset lock 1\n$style 12\n");
2966                 }
2967                 SendToICS(str);
2968                 NotifyFrontendLogin();
2969                 intfSet = TRUE;
2970             }
2971
2972             if (started == STARTED_COMMENT) {
2973                 /* Accumulate characters in comment */
2974                 parse[parse_pos++] = buf[i];
2975                 if (buf[i] == '\n') {
2976                     parse[parse_pos] = NULLCHAR;
2977                     if(chattingPartner>=0) {
2978                         char mess[MSG_SIZ];
2979                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2980                         OutputChatMessage(chattingPartner, mess);
2981                         chattingPartner = -1;
2982                         next_out = i+1; // [HGM] suppress printing in ICS window
2983                     } else
2984                     if(!suppressKibitz) // [HGM] kibitz
2985                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2986                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2987                         int nrDigit = 0, nrAlph = 0, j;
2988                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2989                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2990                         parse[parse_pos] = NULLCHAR;
2991                         // try to be smart: if it does not look like search info, it should go to
2992                         // ICS interaction window after all, not to engine-output window.
2993                         for(j=0; j<parse_pos; j++) { // count letters and digits
2994                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2995                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2996                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2997                         }
2998                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2999                             int depth=0; float score;
3000                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3001                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3002                                 pvInfoList[forwardMostMove-1].depth = depth;
3003                                 pvInfoList[forwardMostMove-1].score = 100*score;
3004                             }
3005                             OutputKibitz(suppressKibitz, parse);
3006                         } else {
3007                             char tmp[MSG_SIZ];
3008                             if(gameMode == IcsObserving) // restore original ICS messages
3009                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3010                             else
3011                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3012                             SendToPlayer(tmp, strlen(tmp));
3013                         }
3014                         next_out = i+1; // [HGM] suppress printing in ICS window
3015                     }
3016                     started = STARTED_NONE;
3017                 } else {
3018                     /* Don't match patterns against characters in comment */
3019                     i++;
3020                     continue;
3021                 }
3022             }
3023             if (started == STARTED_CHATTER) {
3024                 if (buf[i] != '\n') {
3025                     /* Don't match patterns against characters in chatter */
3026                     i++;
3027                     continue;
3028                 }
3029                 started = STARTED_NONE;
3030                 if(suppressKibitz) next_out = i+1;
3031             }
3032
3033             /* Kludge to deal with rcmd protocol */
3034             if (firstTime && looking_at(buf, &i, "\001*")) {
3035                 DisplayFatalError(&buf[1], 0, 1);
3036                 continue;
3037             } else {
3038                 firstTime = FALSE;
3039             }
3040
3041             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3042                 ics_type = ICS_ICC;
3043                 ics_prefix = "/";
3044                 if (appData.debugMode)
3045                   fprintf(debugFP, "ics_type %d\n", ics_type);
3046                 continue;
3047             }
3048             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3049                 ics_type = ICS_FICS;
3050                 ics_prefix = "$";
3051                 if (appData.debugMode)
3052                   fprintf(debugFP, "ics_type %d\n", ics_type);
3053                 continue;
3054             }
3055             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3056                 ics_type = ICS_CHESSNET;
3057                 ics_prefix = "/";
3058                 if (appData.debugMode)
3059                   fprintf(debugFP, "ics_type %d\n", ics_type);
3060                 continue;
3061             }
3062
3063             if (!loggedOn &&
3064                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3065                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3066                  looking_at(buf, &i, "will be \"*\""))) {
3067               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3068               continue;
3069             }
3070
3071             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3072               char buf[MSG_SIZ];
3073               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3074               DisplayIcsInteractionTitle(buf);
3075               have_set_title = TRUE;
3076             }
3077
3078             /* skip finger notes */
3079             if (started == STARTED_NONE &&
3080                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3081                  (buf[i] == '1' && buf[i+1] == '0')) &&
3082                 buf[i+2] == ':' && buf[i+3] == ' ') {
3083               started = STARTED_CHATTER;
3084               i += 3;
3085               continue;
3086             }
3087
3088             oldi = i;
3089             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3090             if(appData.seekGraph) {
3091                 if(soughtPending && MatchSoughtLine(buf+i)) {
3092                     i = strstr(buf+i, "rated") - buf;
3093                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3094                     next_out = leftover_start = i;
3095                     started = STARTED_CHATTER;
3096                     suppressKibitz = TRUE;
3097                     continue;
3098                 }
3099                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3100                         && looking_at(buf, &i, "* ads displayed")) {
3101                     soughtPending = FALSE;
3102                     seekGraphUp = TRUE;
3103                     DrawSeekGraph();
3104                     continue;
3105                 }
3106                 if(appData.autoRefresh) {
3107                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3108                         int s = (ics_type == ICS_ICC); // ICC format differs
3109                         if(seekGraphUp)
3110                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3111                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3112                         looking_at(buf, &i, "*% "); // eat prompt
3113                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3114                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3115                         next_out = i; // suppress
3116                         continue;
3117                     }
3118                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3119                         char *p = star_match[0];
3120                         while(*p) {
3121                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3122                             while(*p && *p++ != ' '); // next
3123                         }
3124                         looking_at(buf, &i, "*% "); // eat prompt
3125                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3126                         next_out = i;
3127                         continue;
3128                     }
3129                 }
3130             }
3131
3132             /* skip formula vars */
3133             if (started == STARTED_NONE &&
3134                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3135               started = STARTED_CHATTER;
3136               i += 3;
3137               continue;
3138             }
3139
3140             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3141             if (appData.autoKibitz && started == STARTED_NONE &&
3142                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3143                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3144                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3145                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3146                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3147                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3148                         suppressKibitz = TRUE;
3149                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3150                         next_out = i;
3151                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3152                                 && (gameMode == IcsPlayingWhite)) ||
3153                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3154                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3155                             started = STARTED_CHATTER; // own kibitz we simply discard
3156                         else {
3157                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3158                             parse_pos = 0; parse[0] = NULLCHAR;
3159                             savingComment = TRUE;
3160                             suppressKibitz = gameMode != IcsObserving ? 2 :
3161                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3162                         }
3163                         continue;
3164                 } else
3165                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3166                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3167                          && atoi(star_match[0])) {
3168                     // suppress the acknowledgements of our own autoKibitz
3169                     char *p;
3170                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3171                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3172                     SendToPlayer(star_match[0], strlen(star_match[0]));
3173                     if(looking_at(buf, &i, "*% ")) // eat prompt
3174                         suppressKibitz = FALSE;
3175                     next_out = i;
3176                     continue;
3177                 }
3178             } // [HGM] kibitz: end of patch
3179
3180             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3181
3182             // [HGM] chat: intercept tells by users for which we have an open chat window
3183             channel = -1;
3184             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3185                                            looking_at(buf, &i, "* whispers:") ||
3186                                            looking_at(buf, &i, "* kibitzes:") ||
3187                                            looking_at(buf, &i, "* shouts:") ||
3188                                            looking_at(buf, &i, "* c-shouts:") ||
3189                                            looking_at(buf, &i, "--> * ") ||
3190                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3191                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3192                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3193                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3194                 int p;
3195                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3196                 chattingPartner = -1;
3197
3198                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3199                 for(p=0; p<MAX_CHAT; p++) {
3200                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3201                     talker[0] = '['; strcat(talker, "] ");
3202                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3203                     chattingPartner = p; break;
3204                     }
3205                 } else
3206                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3207                 for(p=0; p<MAX_CHAT; p++) {
3208                     if(!strcmp("kibitzes", chatPartner[p])) {
3209                         talker[0] = '['; strcat(talker, "] ");
3210                         chattingPartner = p; break;
3211                     }
3212                 } else
3213                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3214                 for(p=0; p<MAX_CHAT; p++) {
3215                     if(!strcmp("whispers", chatPartner[p])) {
3216                         talker[0] = '['; strcat(talker, "] ");
3217                         chattingPartner = p; break;
3218                     }
3219                 } else
3220                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3221                   if(buf[i-8] == '-' && buf[i-3] == 't')
3222                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3223                     if(!strcmp("c-shouts", chatPartner[p])) {
3224                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3225                         chattingPartner = p; break;
3226                     }
3227                   }
3228                   if(chattingPartner < 0)
3229                   for(p=0; p<MAX_CHAT; p++) {
3230                     if(!strcmp("shouts", chatPartner[p])) {
3231                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3232                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3233                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3234                         chattingPartner = p; break;
3235                     }
3236                   }
3237                 }
3238                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3239                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3240                     talker[0] = 0; Colorize(ColorTell, FALSE);
3241                     chattingPartner = p; break;
3242                 }
3243                 if(chattingPartner<0) i = oldi; else {
3244                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3245                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3246                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                     started = STARTED_COMMENT;
3248                     parse_pos = 0; parse[0] = NULLCHAR;
3249                     savingComment = 3 + chattingPartner; // counts as TRUE
3250                     suppressKibitz = TRUE;
3251                     continue;
3252                 }
3253             } // [HGM] chat: end of patch
3254
3255           backup = i;
3256             if (appData.zippyTalk || appData.zippyPlay) {
3257                 /* [DM] Backup address for color zippy lines */
3258 #if ZIPPY
3259                if (loggedOn == TRUE)
3260                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3261                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3262 #endif
3263             } // [DM] 'else { ' deleted
3264                 if (
3265                     /* Regular tells and says */
3266                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3267                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3268                     looking_at(buf, &i, "* says: ") ||
3269                     /* Don't color "message" or "messages" output */
3270                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3271                     looking_at(buf, &i, "*. * at *:*: ") ||
3272                     looking_at(buf, &i, "--* (*:*): ") ||
3273                     /* Message notifications (same color as tells) */
3274                     looking_at(buf, &i, "* has left a message ") ||
3275                     looking_at(buf, &i, "* just sent you a message:\n") ||
3276                     /* Whispers and kibitzes */
3277                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3278                     looking_at(buf, &i, "* kibitzes: ") ||
3279                     /* Channel tells */
3280                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3281
3282                   if (tkind == 1 && strchr(star_match[0], ':')) {
3283                       /* Avoid "tells you:" spoofs in channels */
3284                      tkind = 3;
3285                   }
3286                   if (star_match[0][0] == NULLCHAR ||
3287                       strchr(star_match[0], ' ') ||
3288                       (tkind == 3 && strchr(star_match[1], ' '))) {
3289                     /* Reject bogus matches */
3290                     i = oldi;
3291                   } else {
3292                     if (appData.colorize) {
3293                       if (oldi > next_out) {
3294                         SendToPlayer(&buf[next_out], oldi - next_out);
3295                         next_out = oldi;
3296                       }
3297                       switch (tkind) {
3298                       case 1:
3299                         Colorize(ColorTell, FALSE);
3300                         curColor = ColorTell;
3301                         break;
3302                       case 2:
3303                         Colorize(ColorKibitz, FALSE);
3304                         curColor = ColorKibitz;
3305                         break;
3306                       case 3:
3307                         p = strrchr(star_match[1], '(');
3308                         if (p == NULL) {
3309                           p = star_match[1];
3310                         } else {
3311                           p++;
3312                         }
3313                         if (atoi(p) == 1) {
3314                           Colorize(ColorChannel1, FALSE);
3315                           curColor = ColorChannel1;
3316                         } else {
3317                           Colorize(ColorChannel, FALSE);
3318                           curColor = ColorChannel;
3319                         }
3320                         break;
3321                       case 5:
3322                         curColor = ColorNormal;
3323                         break;
3324                       }
3325                     }
3326                     if (started == STARTED_NONE && appData.autoComment &&
3327                         (gameMode == IcsObserving ||
3328                          gameMode == IcsPlayingWhite ||
3329                          gameMode == IcsPlayingBlack)) {
3330                       parse_pos = i - oldi;
3331                       memcpy(parse, &buf[oldi], parse_pos);
3332                       parse[parse_pos] = NULLCHAR;
3333                       started = STARTED_COMMENT;
3334                       savingComment = TRUE;
3335                     } else {
3336                       started = STARTED_CHATTER;
3337                       savingComment = FALSE;
3338                     }
3339                     loggedOn = TRUE;
3340                     continue;
3341                   }
3342                 }
3343
3344                 if (looking_at(buf, &i, "* s-shouts: ") ||
3345                     looking_at(buf, &i, "* c-shouts: ")) {
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorSShout, FALSE);
3352                         curColor = ColorSShout;
3353                     }
3354                     loggedOn = TRUE;
3355                     started = STARTED_CHATTER;
3356                     continue;
3357                 }
3358
3359                 if (looking_at(buf, &i, "--->")) {
3360                     loggedOn = TRUE;
3361                     continue;
3362                 }
3363
3364                 if (looking_at(buf, &i, "* shouts: ") ||
3365                     looking_at(buf, &i, "--> ")) {
3366                     if (appData.colorize) {
3367                         if (oldi > next_out) {
3368                             SendToPlayer(&buf[next_out], oldi - next_out);
3369                             next_out = oldi;
3370                         }
3371                         Colorize(ColorShout, FALSE);
3372                         curColor = ColorShout;
3373                     }
3374                     loggedOn = TRUE;
3375                     started = STARTED_CHATTER;
3376                     continue;
3377                 }
3378
3379                 if (looking_at( buf, &i, "Challenge:")) {
3380                     if (appData.colorize) {
3381                         if (oldi > next_out) {
3382                             SendToPlayer(&buf[next_out], oldi - next_out);
3383                             next_out = oldi;
3384                         }
3385                         Colorize(ColorChallenge, FALSE);
3386                         curColor = ColorChallenge;
3387                     }
3388                     loggedOn = TRUE;
3389                     continue;
3390                 }
3391
3392                 if (looking_at(buf, &i, "* offers you") ||
3393                     looking_at(buf, &i, "* offers to be") ||
3394                     looking_at(buf, &i, "* would like to") ||
3395                     looking_at(buf, &i, "* requests to") ||
3396                     looking_at(buf, &i, "Your opponent offers") ||
3397                     looking_at(buf, &i, "Your opponent requests")) {
3398
3399                     if (appData.colorize) {
3400                         if (oldi > next_out) {
3401                             SendToPlayer(&buf[next_out], oldi - next_out);
3402                             next_out = oldi;
3403                         }
3404                         Colorize(ColorRequest, FALSE);
3405                         curColor = ColorRequest;
3406                     }
3407                     continue;
3408                 }
3409
3410                 if (looking_at(buf, &i, "* (*) seeking")) {
3411                     if (appData.colorize) {
3412                         if (oldi > next_out) {
3413                             SendToPlayer(&buf[next_out], oldi - next_out);
3414                             next_out = oldi;
3415                         }
3416                         Colorize(ColorSeek, FALSE);
3417                         curColor = ColorSeek;
3418                     }
3419                     continue;
3420             }
3421
3422           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3423
3424             if (looking_at(buf, &i, "\\   ")) {
3425                 if (prevColor != ColorNormal) {
3426                     if (oldi > next_out) {
3427                         SendToPlayer(&buf[next_out], oldi - next_out);
3428                         next_out = oldi;
3429                     }
3430                     Colorize(prevColor, TRUE);
3431                     curColor = prevColor;
3432                 }
3433                 if (savingComment) {
3434                     parse_pos = i - oldi;
3435                     memcpy(parse, &buf[oldi], parse_pos);
3436                     parse[parse_pos] = NULLCHAR;
3437                     started = STARTED_COMMENT;
3438                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3439                         chattingPartner = savingComment - 3; // kludge to remember the box
3440                 } else {
3441                     started = STARTED_CHATTER;
3442                 }
3443                 continue;
3444             }
3445
3446             if (looking_at(buf, &i, "Black Strength :") ||
3447                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3448                 looking_at(buf, &i, "<10>") ||
3449                 looking_at(buf, &i, "#@#")) {
3450                 /* Wrong board style */
3451                 loggedOn = TRUE;
3452                 SendToICS(ics_prefix);
3453                 SendToICS("set style 12\n");
3454                 SendToICS(ics_prefix);
3455                 SendToICS("refresh\n");
3456                 continue;
3457             }
3458
3459             if (looking_at(buf, &i, "login:")) {
3460               if (!have_sent_ICS_logon) {
3461                 if(ICSInitScript())
3462                   have_sent_ICS_logon = 1;
3463                 else // no init script was found
3464                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3465               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3466                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3467               }
3468                 continue;
3469             }
3470
3471             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3472                 (looking_at(buf, &i, "\n<12> ") ||
3473                  looking_at(buf, &i, "<12> "))) {
3474                 loggedOn = TRUE;
3475                 if (oldi > next_out) {
3476                     SendToPlayer(&buf[next_out], oldi - next_out);
3477                 }
3478                 next_out = i;
3479                 started = STARTED_BOARD;
3480                 parse_pos = 0;
3481                 continue;
3482             }
3483
3484             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3485                 looking_at(buf, &i, "<b1> ")) {
3486                 if (oldi > next_out) {
3487                     SendToPlayer(&buf[next_out], oldi - next_out);
3488                 }
3489                 next_out = i;
3490                 started = STARTED_HOLDINGS;
3491                 parse_pos = 0;
3492                 continue;
3493             }
3494
3495             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3496                 loggedOn = TRUE;
3497                 /* Header for a move list -- first line */
3498
3499                 switch (ics_getting_history) {
3500                   case H_FALSE:
3501                     switch (gameMode) {
3502                       case IcsIdle:
3503                       case BeginningOfGame:
3504                         /* User typed "moves" or "oldmoves" while we
3505                            were idle.  Pretend we asked for these
3506                            moves and soak them up so user can step
3507                            through them and/or save them.
3508                            */
3509                         Reset(FALSE, TRUE);
3510                         gameMode = IcsObserving;
3511                         ModeHighlight();
3512                         ics_gamenum = -1;
3513                         ics_getting_history = H_GOT_UNREQ_HEADER;
3514                         break;
3515                       case EditGame: /*?*/
3516                       case EditPosition: /*?*/
3517                         /* Should above feature work in these modes too? */
3518                         /* For now it doesn't */
3519                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3520                         break;
3521                       default:
3522                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3523                         break;
3524                     }
3525                     break;
3526                   case H_REQUESTED:
3527                     /* Is this the right one? */
3528                     if (gameInfo.white && gameInfo.black &&
3529                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3530                         strcmp(gameInfo.black, star_match[2]) == 0) {
3531                         /* All is well */
3532                         ics_getting_history = H_GOT_REQ_HEADER;
3533                     }
3534                     break;
3535                   case H_GOT_REQ_HEADER:
3536                   case H_GOT_UNREQ_HEADER:
3537                   case H_GOT_UNWANTED_HEADER:
3538                   case H_GETTING_MOVES:
3539                     /* Should not happen */
3540                     DisplayError(_("Error gathering move list: two headers"), 0);
3541                     ics_getting_history = H_FALSE;
3542                     break;
3543                 }
3544
3545                 /* Save player ratings into gameInfo if needed */
3546                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3547                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3548                     (gameInfo.whiteRating == -1 ||
3549                      gameInfo.blackRating == -1)) {
3550
3551                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3552                     gameInfo.blackRating = string_to_rating(star_match[3]);
3553                     if (appData.debugMode)
3554                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3555                               gameInfo.whiteRating, gameInfo.blackRating);
3556                 }
3557                 continue;
3558             }
3559
3560             if (looking_at(buf, &i,
3561               "* * match, initial time: * minute*, increment: * second")) {
3562                 /* Header for a move list -- second line */
3563                 /* Initial board will follow if this is a wild game */
3564                 if (gameInfo.event != NULL) free(gameInfo.event);
3565                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3566                 gameInfo.event = StrSave(str);
3567                 /* [HGM] we switched variant. Translate boards if needed. */
3568                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3569                 continue;
3570             }
3571
3572             if (looking_at(buf, &i, "Move  ")) {
3573                 /* Beginning of a move list */
3574                 switch (ics_getting_history) {
3575                   case H_FALSE:
3576                     /* Normally should not happen */
3577                     /* Maybe user hit reset while we were parsing */
3578                     break;
3579                   case H_REQUESTED:
3580                     /* Happens if we are ignoring a move list that is not
3581                      * the one we just requested.  Common if the user
3582                      * tries to observe two games without turning off
3583                      * getMoveList */
3584                     break;
3585                   case H_GETTING_MOVES:
3586                     /* Should not happen */
3587                     DisplayError(_("Error gathering move list: nested"), 0);
3588                     ics_getting_history = H_FALSE;
3589                     break;
3590                   case H_GOT_REQ_HEADER:
3591                     ics_getting_history = H_GETTING_MOVES;
3592                     started = STARTED_MOVES;
3593                     parse_pos = 0;
3594                     if (oldi > next_out) {
3595                         SendToPlayer(&buf[next_out], oldi - next_out);
3596                     }
3597                     break;
3598                   case H_GOT_UNREQ_HEADER:
3599                     ics_getting_history = H_GETTING_MOVES;
3600                     started = STARTED_MOVES_NOHIDE;
3601                     parse_pos = 0;
3602                     break;
3603                   case H_GOT_UNWANTED_HEADER:
3604                     ics_getting_history = H_FALSE;
3605                     break;
3606                 }
3607                 continue;
3608             }
3609
3610             if (looking_at(buf, &i, "% ") ||
3611                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3612                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3613                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3614                     soughtPending = FALSE;
3615                     seekGraphUp = TRUE;
3616                     DrawSeekGraph();
3617                 }
3618                 if(suppressKibitz) next_out = i;
3619                 savingComment = FALSE;
3620                 suppressKibitz = 0;
3621                 switch (started) {
3622                   case STARTED_MOVES:
3623                   case STARTED_MOVES_NOHIDE:
3624                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3625                     parse[parse_pos + i - oldi] = NULLCHAR;
3626                     ParseGameHistory(parse);
3627 #if ZIPPY
3628                     if (appData.zippyPlay && first.initDone) {
3629                         FeedMovesToProgram(&first, forwardMostMove);
3630                         if (gameMode == IcsPlayingWhite) {
3631                             if (WhiteOnMove(forwardMostMove)) {
3632                                 if (first.sendTime) {
3633                                   if (first.useColors) {
3634                                     SendToProgram("black\n", &first);
3635                                   }
3636                                   SendTimeRemaining(&first, TRUE);
3637                                 }
3638                                 if (first.useColors) {
3639                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3640                                 }
3641                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3642                                 first.maybeThinking = TRUE;
3643                             } else {
3644                                 if (first.usePlayother) {
3645                                   if (first.sendTime) {
3646                                     SendTimeRemaining(&first, TRUE);
3647                                   }
3648                                   SendToProgram("playother\n", &first);
3649                                   firstMove = FALSE;
3650                                 } else {
3651                                   firstMove = TRUE;
3652                                 }
3653                             }
3654                         } else if (gameMode == IcsPlayingBlack) {
3655                             if (!WhiteOnMove(forwardMostMove)) {
3656                                 if (first.sendTime) {
3657                                   if (first.useColors) {
3658                                     SendToProgram("white\n", &first);
3659                                   }
3660                                   SendTimeRemaining(&first, FALSE);
3661                                 }
3662                                 if (first.useColors) {
3663                                   SendToProgram("black\n", &first);
3664                                 }
3665                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3666                                 first.maybeThinking = TRUE;
3667                             } else {
3668                                 if (first.usePlayother) {
3669                                   if (first.sendTime) {
3670                                     SendTimeRemaining(&first, FALSE);
3671                                   }
3672                                   SendToProgram("playother\n", &first);
3673                                   firstMove = FALSE;
3674                                 } else {
3675                                   firstMove = TRUE;
3676                                 }
3677                             }
3678                         }
3679                     }
3680 #endif
3681                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3682                         /* Moves came from oldmoves or moves command
3683                            while we weren't doing anything else.
3684                            */
3685                         currentMove = forwardMostMove;
3686                         ClearHighlights();/*!!could figure this out*/
3687                         flipView = appData.flipView;
3688                         DrawPosition(TRUE, boards[currentMove]);
3689                         DisplayBothClocks();
3690                         snprintf(str, MSG_SIZ, "%s %s %s",
3691                                 gameInfo.white, _("vs."),  gameInfo.black);
3692                         DisplayTitle(str);
3693                         gameMode = IcsIdle;
3694                     } else {
3695                         /* Moves were history of an active game */
3696                         if (gameInfo.resultDetails != NULL) {
3697                             free(gameInfo.resultDetails);
3698                             gameInfo.resultDetails = NULL;
3699                         }
3700                     }
3701                     HistorySet(parseList, backwardMostMove,
3702                                forwardMostMove, currentMove-1);
3703                     DisplayMove(currentMove - 1);
3704                     if (started == STARTED_MOVES) next_out = i;
3705                     started = STARTED_NONE;
3706                     ics_getting_history = H_FALSE;
3707                     break;
3708
3709                   case STARTED_OBSERVE:
3710                     started = STARTED_NONE;
3711                     SendToICS(ics_prefix);
3712                     SendToICS("refresh\n");
3713                     break;
3714
3715                   default:
3716                     break;
3717                 }
3718                 if(bookHit) { // [HGM] book: simulate book reply
3719                     static char bookMove[MSG_SIZ]; // a bit generous?
3720
3721                     programStats.nodes = programStats.depth = programStats.time =
3722                     programStats.score = programStats.got_only_move = 0;
3723                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3724
3725                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3726                     strcat(bookMove, bookHit);
3727                     HandleMachineMove(bookMove, &first);
3728                 }
3729                 continue;
3730             }
3731
3732             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3733                  started == STARTED_HOLDINGS ||
3734                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3735                 /* Accumulate characters in move list or board */
3736                 parse[parse_pos++] = buf[i];
3737             }
3738
3739             /* Start of game messages.  Mostly we detect start of game
3740                when the first board image arrives.  On some versions
3741                of the ICS, though, we need to do a "refresh" after starting
3742                to observe in order to get the current board right away. */
3743             if (looking_at(buf, &i, "Adding game * to observation list")) {
3744                 started = STARTED_OBSERVE;
3745                 continue;
3746             }
3747
3748             /* Handle auto-observe */
3749             if (appData.autoObserve &&
3750                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3751                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3752                 char *player;
3753                 /* Choose the player that was highlighted, if any. */
3754                 if (star_match[0][0] == '\033' ||
3755                     star_match[1][0] != '\033') {
3756                     player = star_match[0];
3757                 } else {
3758                     player = star_match[2];
3759                 }
3760                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3761                         ics_prefix, StripHighlightAndTitle(player));
3762                 SendToICS(str);
3763
3764                 /* Save ratings from notify string */
3765                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3766                 player1Rating = string_to_rating(star_match[1]);
3767                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3768                 player2Rating = string_to_rating(star_match[3]);
3769
3770                 if (appData.debugMode)
3771                   fprintf(debugFP,
3772                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3773                           player1Name, player1Rating,
3774                           player2Name, player2Rating);
3775
3776                 continue;
3777             }
3778
3779             /* Deal with automatic examine mode after a game,
3780                and with IcsObserving -> IcsExamining transition */
3781             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3782                 looking_at(buf, &i, "has made you an examiner of game *")) {
3783
3784                 int gamenum = atoi(star_match[0]);
3785                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3786                     gamenum == ics_gamenum) {
3787                     /* We were already playing or observing this game;
3788                        no need to refetch history */
3789                     gameMode = IcsExamining;
3790                     if (pausing) {
3791                         pauseExamForwardMostMove = forwardMostMove;
3792                     } else if (currentMove < forwardMostMove) {
3793                         ForwardInner(forwardMostMove);
3794                     }
3795                 } else {
3796                     /* I don't think this case really can happen */
3797                     SendToICS(ics_prefix);
3798                     SendToICS("refresh\n");
3799                 }
3800                 continue;
3801             }
3802
3803             /* Error messages */
3804 //          if (ics_user_moved) {
3805             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3806                 if (looking_at(buf, &i, "Illegal move") ||
3807                     looking_at(buf, &i, "Not a legal move") ||
3808                     looking_at(buf, &i, "Your king is in check") ||
3809                     looking_at(buf, &i, "It isn't your turn") ||
3810                     looking_at(buf, &i, "It is not your move")) {
3811                     /* Illegal move */
3812                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3813                         currentMove = forwardMostMove-1;
3814                         DisplayMove(currentMove - 1); /* before DMError */
3815                         DrawPosition(FALSE, boards[currentMove]);
3816                         SwitchClocks(forwardMostMove-1); // [HGM] race
3817                         DisplayBothClocks();
3818                     }
3819                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3820                     ics_user_moved = 0;
3821                     continue;
3822                 }
3823             }
3824
3825             if (looking_at(buf, &i, "still have time") ||
3826                 looking_at(buf, &i, "not out of time") ||
3827                 looking_at(buf, &i, "either player is out of time") ||
3828                 looking_at(buf, &i, "has timeseal; checking")) {
3829                 /* We must have called his flag a little too soon */
3830                 whiteFlag = blackFlag = FALSE;
3831                 continue;
3832             }
3833
3834             if (looking_at(buf, &i, "added * seconds to") ||
3835                 looking_at(buf, &i, "seconds were added to")) {
3836                 /* Update the clocks */
3837                 SendToICS(ics_prefix);
3838                 SendToICS("refresh\n");
3839                 continue;
3840             }
3841
3842             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3843                 ics_clock_paused = TRUE;
3844                 StopClocks();
3845                 continue;
3846             }
3847
3848             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3849                 ics_clock_paused = FALSE;
3850                 StartClocks();
3851                 continue;
3852             }
3853
3854             /* Grab player ratings from the Creating: message.
3855                Note we have to check for the special case when
3856                the ICS inserts things like [white] or [black]. */
3857             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3858                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3859                 /* star_matches:
3860                    0    player 1 name (not necessarily white)
3861                    1    player 1 rating
3862                    2    empty, white, or black (IGNORED)
3863                    3    player 2 name (not necessarily black)
3864                    4    player 2 rating
3865
3866                    The names/ratings are sorted out when the game
3867                    actually starts (below).
3868                 */
3869                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3870                 player1Rating = string_to_rating(star_match[1]);
3871                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3872                 player2Rating = string_to_rating(star_match[4]);
3873
3874                 if (appData.debugMode)
3875                   fprintf(debugFP,
3876                           "Ratings from 'Creating:' %s %d, %s %d\n",
3877                           player1Name, player1Rating,
3878                           player2Name, player2Rating);
3879
3880                 continue;
3881             }
3882
3883             /* Improved generic start/end-of-game messages */
3884             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3885                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3886                 /* If tkind == 0: */
3887                 /* star_match[0] is the game number */
3888                 /*           [1] is the white player's name */
3889                 /*           [2] is the black player's name */
3890                 /* For end-of-game: */
3891                 /*           [3] is the reason for the game end */
3892                 /*           [4] is a PGN end game-token, preceded by " " */
3893                 /* For start-of-game: */
3894                 /*           [3] begins with "Creating" or "Continuing" */
3895                 /*           [4] is " *" or empty (don't care). */
3896                 int gamenum = atoi(star_match[0]);
3897                 char *whitename, *blackname, *why, *endtoken;
3898                 ChessMove endtype = EndOfFile;
3899
3900                 if (tkind == 0) {
3901                   whitename = star_match[1];
3902                   blackname = star_match[2];
3903                   why = star_match[3];
3904                   endtoken = star_match[4];
3905                 } else {
3906                   whitename = star_match[1];
3907                   blackname = star_match[3];
3908                   why = star_match[5];
3909                   endtoken = star_match[6];
3910                 }
3911
3912                 /* Game start messages */
3913                 if (strncmp(why, "Creating ", 9) == 0 ||
3914                     strncmp(why, "Continuing ", 11) == 0) {
3915                     gs_gamenum = gamenum;
3916                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3917                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3918                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3919 #if ZIPPY
3920                     if (appData.zippyPlay) {
3921                         ZippyGameStart(whitename, blackname);
3922                     }
3923 #endif /*ZIPPY*/
3924                     partnerBoardValid = FALSE; // [HGM] bughouse
3925                     continue;
3926                 }
3927
3928                 /* Game end messages */
3929                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3930                     ics_gamenum != gamenum) {
3931                     continue;
3932                 }
3933                 while (endtoken[0] == ' ') endtoken++;
3934                 switch (endtoken[0]) {
3935                   case '*':
3936                   default:
3937                     endtype = GameUnfinished;
3938                     break;
3939                   case '0':
3940                     endtype = BlackWins;
3941                     break;
3942                   case '1':
3943                     if (endtoken[1] == '/')
3944                       endtype = GameIsDrawn;
3945                     else
3946                       endtype = WhiteWins;
3947                     break;
3948                 }
3949                 GameEnds(endtype, why, GE_ICS);
3950 #if ZIPPY
3951                 if (appData.zippyPlay && first.initDone) {
3952                     ZippyGameEnd(endtype, why);
3953                     if (first.pr == NoProc) {
3954                       /* Start the next process early so that we'll
3955                          be ready for the next challenge */
3956                       StartChessProgram(&first);
3957                     }
3958                     /* Send "new" early, in case this command takes
3959                        a long time to finish, so that we'll be ready
3960                        for the next challenge. */
3961                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3962                     Reset(TRUE, TRUE);
3963                 }
3964 #endif /*ZIPPY*/
3965                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3966                 continue;
3967             }
3968
3969             if (looking_at(buf, &i, "Removing game * from observation") ||
3970                 looking_at(buf, &i, "no longer observing game *") ||
3971                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3972                 if (gameMode == IcsObserving &&
3973                     atoi(star_match[0]) == ics_gamenum)
3974                   {
3975                       /* icsEngineAnalyze */
3976                       if (appData.icsEngineAnalyze) {
3977                             ExitAnalyzeMode();
3978                             ModeHighlight();
3979                       }
3980                       StopClocks();
3981                       gameMode = IcsIdle;
3982                       ics_gamenum = -1;
3983                       ics_user_moved = FALSE;
3984                   }
3985                 continue;
3986             }
3987
3988             if (looking_at(buf, &i, "no longer examining game *")) {
3989                 if (gameMode == IcsExamining &&
3990                     atoi(star_match[0]) == ics_gamenum)
3991                   {
3992                       gameMode = IcsIdle;
3993                       ics_gamenum = -1;
3994                       ics_user_moved = FALSE;
3995                   }
3996                 continue;
3997             }
3998
3999             /* Advance leftover_start past any newlines we find,
4000                so only partial lines can get reparsed */
4001             if (looking_at(buf, &i, "\n")) {
4002                 prevColor = curColor;
4003                 if (curColor != ColorNormal) {
4004                     if (oldi > next_out) {
4005                         SendToPlayer(&buf[next_out], oldi - next_out);
4006                         next_out = oldi;
4007                     }
4008                     Colorize(ColorNormal, FALSE);
4009                     curColor = ColorNormal;
4010                 }
4011                 if (started == STARTED_BOARD) {
4012                     started = STARTED_NONE;
4013                     parse[parse_pos] = NULLCHAR;
4014                     ParseBoard12(parse);
4015                     ics_user_moved = 0;
4016
4017                     /* Send premove here */
4018                     if (appData.premove) {
4019                       char str[MSG_SIZ];
4020                       if (currentMove == 0 &&
4021                           gameMode == IcsPlayingWhite &&
4022                           appData.premoveWhite) {
4023                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4024                         if (appData.debugMode)
4025                           fprintf(debugFP, "Sending premove:\n");
4026                         SendToICS(str);
4027                       } else if (currentMove == 1 &&
4028                                  gameMode == IcsPlayingBlack &&
4029                                  appData.premoveBlack) {
4030                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4031                         if (appData.debugMode)
4032                           fprintf(debugFP, "Sending premove:\n");
4033                         SendToICS(str);
4034                       } else if (gotPremove) {
4035                         gotPremove = 0;
4036                         ClearPremoveHighlights();
4037                         if (appData.debugMode)
4038                           fprintf(debugFP, "Sending premove:\n");
4039                           UserMoveEvent(premoveFromX, premoveFromY,
4040                                         premoveToX, premoveToY,
4041                                         premovePromoChar);
4042                       }
4043                     }
4044
4045                     /* Usually suppress following prompt */
4046                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4047                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4048                         if (looking_at(buf, &i, "*% ")) {
4049                             savingComment = FALSE;
4050                             suppressKibitz = 0;
4051                         }
4052                     }
4053                     next_out = i;
4054                 } else if (started == STARTED_HOLDINGS) {
4055                     int gamenum;
4056                     char new_piece[MSG_SIZ];
4057                     started = STARTED_NONE;
4058                     parse[parse_pos] = NULLCHAR;
4059                     if (appData.debugMode)
4060                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4061                                                         parse, currentMove);
4062                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4063                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4064                         if (gameInfo.variant == VariantNormal) {
4065                           /* [HGM] We seem to switch variant during a game!
4066                            * Presumably no holdings were displayed, so we have
4067                            * to move the position two files to the right to
4068                            * create room for them!
4069                            */
4070                           VariantClass newVariant;
4071                           switch(gameInfo.boardWidth) { // base guess on board width
4072                                 case 9:  newVariant = VariantShogi; break;
4073                                 case 10: newVariant = VariantGreat; break;
4074                                 default: newVariant = VariantCrazyhouse; break;
4075                           }
4076                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4077                           /* Get a move list just to see the header, which
4078                              will tell us whether this is really bug or zh */
4079                           if (ics_getting_history == H_FALSE) {
4080                             ics_getting_history = H_REQUESTED;
4081                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4082                             SendToICS(str);
4083                           }
4084                         }
4085                         new_piece[0] = NULLCHAR;
4086                         sscanf(parse, "game %d white [%s black [%s <- %s",
4087                                &gamenum, white_holding, black_holding,
4088                                new_piece);
4089                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4090                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4091                         /* [HGM] copy holdings to board holdings area */
4092                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4093                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4094                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4095 #if ZIPPY
4096                         if (appData.zippyPlay && first.initDone) {
4097                             ZippyHoldings(white_holding, black_holding,
4098                                           new_piece);
4099                         }
4100 #endif /*ZIPPY*/
4101                         if (tinyLayout || smallLayout) {
4102                             char wh[16], bh[16];
4103                             PackHolding(wh, white_holding);
4104                             PackHolding(bh, black_holding);
4105                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4106                                     gameInfo.white, gameInfo.black);
4107                         } else {
4108                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4109                                     gameInfo.white, white_holding, _("vs."),
4110                                     gameInfo.black, black_holding);
4111                         }
4112                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4113                         DrawPosition(FALSE, boards[currentMove]);
4114                         DisplayTitle(str);
4115                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4116                         sscanf(parse, "game %d white [%s black [%s <- %s",
4117                                &gamenum, white_holding, black_holding,
4118                                new_piece);
4119                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4120                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4121                         /* [HGM] copy holdings to partner-board holdings area */
4122                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4123                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4124                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4125                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4126                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4127                       }
4128                     }
4129                     /* Suppress following prompt */
4130                     if (looking_at(buf, &i, "*% ")) {
4131                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4132                         savingComment = FALSE;
4133                         suppressKibitz = 0;
4134                     }
4135                     next_out = i;
4136                 }
4137                 continue;
4138             }
4139
4140             i++;                /* skip unparsed character and loop back */
4141         }
4142
4143         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4144 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4145 //          SendToPlayer(&buf[next_out], i - next_out);
4146             started != STARTED_HOLDINGS && leftover_start > next_out) {
4147             SendToPlayer(&buf[next_out], leftover_start - next_out);
4148             next_out = i;
4149         }
4150
4151         leftover_len = buf_len - leftover_start;
4152         /* if buffer ends with something we couldn't parse,
4153            reparse it after appending the next read */
4154
4155     } else if (count == 0) {
4156         RemoveInputSource(isr);
4157         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4158     } else {
4159         DisplayFatalError(_("Error reading from ICS"), error, 1);
4160     }
4161 }
4162
4163
4164 /* Board style 12 looks like this:
4165
4166    <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
4167
4168  * The "<12> " is stripped before it gets to this routine.  The two
4169  * trailing 0's (flip state and clock ticking) are later addition, and
4170  * some chess servers may not have them, or may have only the first.
4171  * Additional trailing fields may be added in the future.
4172  */
4173
4174 #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"
4175
4176 #define RELATION_OBSERVING_PLAYED    0
4177 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4178 #define RELATION_PLAYING_MYMOVE      1
4179 #define RELATION_PLAYING_NOTMYMOVE  -1
4180 #define RELATION_EXAMINING           2
4181 #define RELATION_ISOLATED_BOARD     -3
4182 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4183
4184 void
4185 ParseBoard12 (char *string)
4186 {
4187 #if ZIPPY
4188     int i, takeback;
4189     char *bookHit = NULL; // [HGM] book
4190 #endif
4191     GameMode newGameMode;
4192     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4193     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4194     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4195     char to_play, board_chars[200];
4196     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4197     char black[32], white[32];
4198     Board board;
4199     int prevMove = currentMove;
4200     int ticking = 2;
4201     ChessMove moveType;
4202     int fromX, fromY, toX, toY;
4203     char promoChar;
4204     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4205     Boolean weird = FALSE, reqFlag = FALSE;
4206
4207     fromX = fromY = toX = toY = -1;
4208
4209     newGame = FALSE;
4210
4211     if (appData.debugMode)
4212       fprintf(debugFP, _("Parsing board: %s\n"), string);
4213
4214     move_str[0] = NULLCHAR;
4215     elapsed_time[0] = NULLCHAR;
4216     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4217         int  i = 0, j;
4218         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4219             if(string[i] == ' ') { ranks++; files = 0; }
4220             else files++;
4221             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4222             i++;
4223         }
4224         for(j = 0; j <i; j++) board_chars[j] = string[j];
4225         board_chars[i] = '\0';
4226         string += i + 1;
4227     }
4228     n = sscanf(string, PATTERN, &to_play, &double_push,
4229                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4230                &gamenum, white, black, &relation, &basetime, &increment,
4231                &white_stren, &black_stren, &white_time, &black_time,
4232                &moveNum, str, elapsed_time, move_str, &ics_flip,
4233                &ticking);
4234
4235     if (n < 21) {
4236         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4237         DisplayError(str, 0);
4238         return;
4239     }
4240
4241     /* Convert the move number to internal form */
4242     moveNum = (moveNum - 1) * 2;
4243     if (to_play == 'B') moveNum++;
4244     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4245       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4246                         0, 1);
4247       return;
4248     }
4249
4250     switch (relation) {
4251       case RELATION_OBSERVING_PLAYED:
4252       case RELATION_OBSERVING_STATIC:
4253         if (gamenum == -1) {
4254             /* Old ICC buglet */
4255             relation = RELATION_OBSERVING_STATIC;
4256         }
4257         newGameMode = IcsObserving;
4258         break;
4259       case RELATION_PLAYING_MYMOVE:
4260       case RELATION_PLAYING_NOTMYMOVE:
4261         newGameMode =
4262           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4263             IcsPlayingWhite : IcsPlayingBlack;
4264         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4265         break;
4266       case RELATION_EXAMINING:
4267         newGameMode = IcsExamining;
4268         break;
4269       case RELATION_ISOLATED_BOARD:
4270       default:
4271         /* Just display this board.  If user was doing something else,
4272            we will forget about it until the next board comes. */
4273         newGameMode = IcsIdle;
4274         break;
4275       case RELATION_STARTING_POSITION:
4276         newGameMode = gameMode;
4277         break;
4278     }
4279
4280     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4281         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4282          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4283       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4284       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4285       static int lastBgGame = -1;
4286       char *toSqr;
4287       for (k = 0; k < ranks; k++) {
4288         for (j = 0; j < files; j++)
4289           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4290         if(gameInfo.holdingsWidth > 1) {
4291              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4292              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4293         }
4294       }
4295       CopyBoard(partnerBoard, board);
4296       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4297         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4298         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4299       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4300       if(toSqr = strchr(str, '-')) {
4301         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4302         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4303       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4304       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4305       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4306       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4307       if(twoBoards) {
4308           DisplayWhiteClock(white_time*fac, to_play == 'W');
4309           DisplayBlackClock(black_time*fac, to_play != 'W');
4310           activePartner = to_play;
4311           if(gamenum != lastBgGame) {
4312               char buf[MSG_SIZ];
4313               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4314               DisplayTitle(buf);
4315           }
4316           lastBgGame = gamenum;
4317           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4318                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4319       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4320                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4321       DisplayMessage(partnerStatus, "");
4322         partnerBoardValid = TRUE;
4323       return;
4324     }
4325
4326     if(appData.dualBoard && appData.bgObserve) {
4327         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4328             SendToICS(ics_prefix), SendToICS("pobserve\n");
4329         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4330             char buf[MSG_SIZ];
4331             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4332             SendToICS(buf);
4333         }
4334     }
4335
4336     /* Modify behavior for initial board display on move listing
4337        of wild games.
4338        */
4339     switch (ics_getting_history) {
4340       case H_FALSE:
4341       case H_REQUESTED:
4342         break;
4343       case H_GOT_REQ_HEADER:
4344       case H_GOT_UNREQ_HEADER:
4345         /* This is the initial position of the current game */
4346         gamenum = ics_gamenum;
4347         moveNum = 0;            /* old ICS bug workaround */
4348         if (to_play == 'B') {
4349           startedFromSetupPosition = TRUE;
4350           blackPlaysFirst = TRUE;
4351           moveNum = 1;
4352           if (forwardMostMove == 0) forwardMostMove = 1;
4353           if (backwardMostMove == 0) backwardMostMove = 1;
4354           if (currentMove == 0) currentMove = 1;
4355         }
4356         newGameMode = gameMode;
4357         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4358         break;
4359       case H_GOT_UNWANTED_HEADER:
4360         /* This is an initial board that we don't want */
4361         return;
4362       case H_GETTING_MOVES:
4363         /* Should not happen */
4364         DisplayError(_("Error gathering move list: extra board"), 0);
4365         ics_getting_history = H_FALSE;
4366         return;
4367     }
4368
4369    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4370                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4371                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4372      /* [HGM] We seem to have switched variant unexpectedly
4373       * Try to guess new variant from board size
4374       */
4375           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4376           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4377           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4378           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4379           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4380           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4381           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4382           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4383           /* Get a move list just to see the header, which
4384              will tell us whether this is really bug or zh */
4385           if (ics_getting_history == H_FALSE) {
4386             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4387             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4388             SendToICS(str);
4389           }
4390     }
4391
4392     /* Take action if this is the first board of a new game, or of a
4393        different game than is currently being displayed.  */
4394     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4395         relation == RELATION_ISOLATED_BOARD) {
4396
4397         /* Forget the old game and get the history (if any) of the new one */
4398         if (gameMode != BeginningOfGame) {
4399           Reset(TRUE, TRUE);
4400         }
4401         newGame = TRUE;
4402         if (appData.autoRaiseBoard) BoardToTop();
4403         prevMove = -3;
4404         if (gamenum == -1) {
4405             newGameMode = IcsIdle;
4406         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4407                    appData.getMoveList && !reqFlag) {
4408             /* Need to get game history */
4409             ics_getting_history = H_REQUESTED;
4410             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4411             SendToICS(str);
4412         }
4413
4414         /* Initially flip the board to have black on the bottom if playing
4415            black or if the ICS flip flag is set, but let the user change
4416            it with the Flip View button. */
4417         flipView = appData.autoFlipView ?
4418           (newGameMode == IcsPlayingBlack) || ics_flip :
4419           appData.flipView;
4420
4421         /* Done with values from previous mode; copy in new ones */
4422         gameMode = newGameMode;
4423         ModeHighlight();
4424         ics_gamenum = gamenum;
4425         if (gamenum == gs_gamenum) {
4426             int klen = strlen(gs_kind);
4427             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4428             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4429             gameInfo.event = StrSave(str);
4430         } else {
4431             gameInfo.event = StrSave("ICS game");
4432         }
4433         gameInfo.site = StrSave(appData.icsHost);
4434         gameInfo.date = PGNDate();
4435         gameInfo.round = StrSave("-");
4436         gameInfo.white = StrSave(white);
4437         gameInfo.black = StrSave(black);
4438         timeControl = basetime * 60 * 1000;
4439         timeControl_2 = 0;
4440         timeIncrement = increment * 1000;
4441         movesPerSession = 0;
4442         gameInfo.timeControl = TimeControlTagValue();
4443         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4444   if (appData.debugMode) {
4445     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4446     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4447     setbuf(debugFP, NULL);
4448   }
4449
4450         gameInfo.outOfBook = NULL;
4451
4452         /* Do we have the ratings? */
4453         if (strcmp(player1Name, white) == 0 &&
4454             strcmp(player2Name, black) == 0) {
4455             if (appData.debugMode)
4456               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4457                       player1Rating, player2Rating);
4458             gameInfo.whiteRating = player1Rating;
4459             gameInfo.blackRating = player2Rating;
4460         } else if (strcmp(player2Name, white) == 0 &&
4461                    strcmp(player1Name, black) == 0) {
4462             if (appData.debugMode)
4463               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4464                       player2Rating, player1Rating);
4465             gameInfo.whiteRating = player2Rating;
4466             gameInfo.blackRating = player1Rating;
4467         }
4468         player1Name[0] = player2Name[0] = NULLCHAR;
4469
4470         /* Silence shouts if requested */
4471         if (appData.quietPlay &&
4472             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4473             SendToICS(ics_prefix);
4474             SendToICS("set shout 0\n");
4475         }
4476     }
4477
4478     /* Deal with midgame name changes */
4479     if (!newGame) {
4480         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4481             if (gameInfo.white) free(gameInfo.white);
4482             gameInfo.white = StrSave(white);
4483         }
4484         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4485             if (gameInfo.black) free(gameInfo.black);
4486             gameInfo.black = StrSave(black);
4487         }
4488     }
4489
4490     /* Throw away game result if anything actually changes in examine mode */
4491     if (gameMode == IcsExamining && !newGame) {
4492         gameInfo.result = GameUnfinished;
4493         if (gameInfo.resultDetails != NULL) {
4494             free(gameInfo.resultDetails);
4495             gameInfo.resultDetails = NULL;
4496         }
4497     }
4498
4499     /* In pausing && IcsExamining mode, we ignore boards coming
4500        in if they are in a different variation than we are. */
4501     if (pauseExamInvalid) return;
4502     if (pausing && gameMode == IcsExamining) {
4503         if (moveNum <= pauseExamForwardMostMove) {
4504             pauseExamInvalid = TRUE;
4505             forwardMostMove = pauseExamForwardMostMove;
4506             return;
4507         }
4508     }
4509
4510   if (appData.debugMode) {
4511     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4512   }
4513     /* Parse the board */
4514     for (k = 0; k < ranks; k++) {
4515       for (j = 0; j < files; j++)
4516         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4517       if(gameInfo.holdingsWidth > 1) {
4518            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4519            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4520       }
4521     }
4522     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4523       board[5][BOARD_RGHT+1] = WhiteAngel;
4524       board[6][BOARD_RGHT+1] = WhiteMarshall;
4525       board[1][0] = BlackMarshall;
4526       board[2][0] = BlackAngel;
4527       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4528     }
4529     CopyBoard(boards[moveNum], board);
4530     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4531     if (moveNum == 0) {
4532         startedFromSetupPosition =
4533           !CompareBoards(board, initialPosition);
4534         if(startedFromSetupPosition)
4535             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4536     }
4537
4538     /* [HGM] Set castling rights. Take the outermost Rooks,
4539        to make it also work for FRC opening positions. Note that board12
4540        is really defective for later FRC positions, as it has no way to
4541        indicate which Rook can castle if they are on the same side of King.
4542        For the initial position we grant rights to the outermost Rooks,
4543        and remember thos rights, and we then copy them on positions
4544        later in an FRC game. This means WB might not recognize castlings with
4545        Rooks that have moved back to their original position as illegal,
4546        but in ICS mode that is not its job anyway.
4547     */
4548     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4549     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4550
4551         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4552             if(board[0][i] == WhiteRook) j = i;
4553         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4554         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4555             if(board[0][i] == WhiteRook) j = i;
4556         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4557         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4558             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4559         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4560         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4561             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4562         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4563
4564         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4565         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4566         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4567             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4568         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4569             if(board[BOARD_HEIGHT-1][k] == bKing)
4570                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4571         if(gameInfo.variant == VariantTwoKings) {
4572             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4573             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4574             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4575         }
4576     } else { int r;
4577         r = boards[moveNum][CASTLING][0] = initialRights[0];
4578         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4579         r = boards[moveNum][CASTLING][1] = initialRights[1];
4580         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4581         r = boards[moveNum][CASTLING][3] = initialRights[3];
4582         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4583         r = boards[moveNum][CASTLING][4] = initialRights[4];
4584         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4585         /* wildcastle kludge: always assume King has rights */
4586         r = boards[moveNum][CASTLING][2] = initialRights[2];
4587         r = boards[moveNum][CASTLING][5] = initialRights[5];
4588     }
4589     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4590     boards[moveNum][EP_STATUS] = EP_NONE;
4591     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4592     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4593     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4594
4595
4596     if (ics_getting_history == H_GOT_REQ_HEADER ||
4597         ics_getting_history == H_GOT_UNREQ_HEADER) {
4598         /* This was an initial position from a move list, not
4599            the current position */
4600         return;
4601     }
4602
4603     /* Update currentMove and known move number limits */
4604     newMove = newGame || moveNum > forwardMostMove;
4605
4606     if (newGame) {
4607         forwardMostMove = backwardMostMove = currentMove = moveNum;
4608         if (gameMode == IcsExamining && moveNum == 0) {
4609           /* Workaround for ICS limitation: we are not told the wild
4610              type when starting to examine a game.  But if we ask for
4611              the move list, the move list header will tell us */
4612             ics_getting_history = H_REQUESTED;
4613             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4614             SendToICS(str);
4615         }
4616     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4617                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4618 #if ZIPPY
4619         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4620         /* [HGM] applied this also to an engine that is silently watching        */
4621         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4622             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4623             gameInfo.variant == currentlyInitializedVariant) {
4624           takeback = forwardMostMove - moveNum;
4625           for (i = 0; i < takeback; i++) {
4626             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4627             SendToProgram("undo\n", &first);
4628           }
4629         }
4630 #endif
4631
4632         forwardMostMove = moveNum;
4633         if (!pausing || currentMove > forwardMostMove)
4634           currentMove = forwardMostMove;
4635     } else {
4636         /* New part of history that is not contiguous with old part */
4637         if (pausing && gameMode == IcsExamining) {
4638             pauseExamInvalid = TRUE;
4639             forwardMostMove = pauseExamForwardMostMove;
4640             return;
4641         }
4642         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4643 #if ZIPPY
4644             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4645                 // [HGM] when we will receive the move list we now request, it will be
4646                 // fed to the engine from the first move on. So if the engine is not
4647                 // in the initial position now, bring it there.
4648                 InitChessProgram(&first, 0);
4649             }
4650 #endif
4651             ics_getting_history = H_REQUESTED;
4652             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4653             SendToICS(str);
4654         }
4655         forwardMostMove = backwardMostMove = currentMove = moveNum;
4656     }
4657
4658     /* Update the clocks */
4659     if (strchr(elapsed_time, '.')) {
4660       /* Time is in ms */
4661       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4662       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4663     } else {
4664       /* Time is in seconds */
4665       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4666       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4667     }
4668
4669
4670 #if ZIPPY
4671     if (appData.zippyPlay && newGame &&
4672         gameMode != IcsObserving && gameMode != IcsIdle &&
4673         gameMode != IcsExamining)
4674       ZippyFirstBoard(moveNum, basetime, increment);
4675 #endif
4676
4677     /* Put the move on the move list, first converting
4678        to canonical algebraic form. */
4679     if (moveNum > 0) {
4680   if (appData.debugMode) {
4681     if (appData.debugMode) { int f = forwardMostMove;
4682         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4683                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4684                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4685     }
4686     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4687     fprintf(debugFP, "moveNum = %d\n", moveNum);
4688     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4689     setbuf(debugFP, NULL);
4690   }
4691         if (moveNum <= backwardMostMove) {
4692             /* We don't know what the board looked like before
4693                this move.  Punt. */
4694           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4695             strcat(parseList[moveNum - 1], " ");
4696             strcat(parseList[moveNum - 1], elapsed_time);
4697             moveList[moveNum - 1][0] = NULLCHAR;
4698         } else if (strcmp(move_str, "none") == 0) {
4699             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4700             /* Again, we don't know what the board looked like;
4701                this is really the start of the game. */
4702             parseList[moveNum - 1][0] = NULLCHAR;
4703             moveList[moveNum - 1][0] = NULLCHAR;
4704             backwardMostMove = moveNum;
4705             startedFromSetupPosition = TRUE;
4706             fromX = fromY = toX = toY = -1;
4707         } else {
4708           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4709           //                 So we parse the long-algebraic move string in stead of the SAN move
4710           int valid; char buf[MSG_SIZ], *prom;
4711
4712           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4713                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4714           // str looks something like "Q/a1-a2"; kill the slash
4715           if(str[1] == '/')
4716             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4717           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4718           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4719                 strcat(buf, prom); // long move lacks promo specification!
4720           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4721                 if(appData.debugMode)
4722                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4723                 safeStrCpy(move_str, buf, MSG_SIZ);
4724           }
4725           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4726                                 &fromX, &fromY, &toX, &toY, &promoChar)
4727                || ParseOneMove(buf, moveNum - 1, &moveType,
4728                                 &fromX, &fromY, &toX, &toY, &promoChar);
4729           // end of long SAN patch
4730           if (valid) {
4731             (void) CoordsToAlgebraic(boards[moveNum - 1],
4732                                      PosFlags(moveNum - 1),
4733                                      fromY, fromX, toY, toX, promoChar,
4734                                      parseList[moveNum-1]);
4735             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4736               case MT_NONE:
4737               case MT_STALEMATE:
4738               default:
4739                 break;
4740               case MT_CHECK:
4741                 if(gameInfo.variant != VariantShogi)
4742                     strcat(parseList[moveNum - 1], "+");
4743                 break;
4744               case MT_CHECKMATE:
4745               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4746                 strcat(parseList[moveNum - 1], "#");
4747                 break;
4748             }
4749             strcat(parseList[moveNum - 1], " ");
4750             strcat(parseList[moveNum - 1], elapsed_time);
4751             /* currentMoveString is set as a side-effect of ParseOneMove */
4752             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4753             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4754             strcat(moveList[moveNum - 1], "\n");
4755
4756             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4757                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4758               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4759                 ChessSquare old, new = boards[moveNum][k][j];
4760                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4761                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4762                   if(old == new) continue;
4763                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4764                   else if(new == WhiteWazir || new == BlackWazir) {
4765                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4766                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4767                       else boards[moveNum][k][j] = old; // preserve type of Gold
4768                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4769                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4770               }
4771           } else {
4772             /* Move from ICS was illegal!?  Punt. */
4773             if (appData.debugMode) {
4774               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4775               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4776             }
4777             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4778             strcat(parseList[moveNum - 1], " ");
4779             strcat(parseList[moveNum - 1], elapsed_time);
4780             moveList[moveNum - 1][0] = NULLCHAR;
4781             fromX = fromY = toX = toY = -1;
4782           }
4783         }
4784   if (appData.debugMode) {
4785     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4786     setbuf(debugFP, NULL);
4787   }
4788
4789 #if ZIPPY
4790         /* Send move to chess program (BEFORE animating it). */
4791         if (appData.zippyPlay && !newGame && newMove &&
4792            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4793
4794             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4795                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4796                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4797                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4798                             move_str);
4799                     DisplayError(str, 0);
4800                 } else {
4801                     if (first.sendTime) {
4802                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4803                     }
4804                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4805                     if (firstMove && !bookHit) {
4806                         firstMove = FALSE;
4807                         if (first.useColors) {
4808                           SendToProgram(gameMode == IcsPlayingWhite ?
4809                                         "white\ngo\n" :
4810                                         "black\ngo\n", &first);
4811                         } else {
4812                           SendToProgram("go\n", &first);
4813                         }
4814                         first.maybeThinking = TRUE;
4815                     }
4816                 }
4817             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4818               if (moveList[moveNum - 1][0] == NULLCHAR) {
4819                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4820                 DisplayError(str, 0);
4821               } else {
4822                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4823                 SendMoveToProgram(moveNum - 1, &first);
4824               }
4825             }
4826         }
4827 #endif
4828     }
4829
4830     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4831         /* If move comes from a remote source, animate it.  If it
4832            isn't remote, it will have already been animated. */
4833         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4834             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4835         }
4836         if (!pausing && appData.highlightLastMove) {
4837             SetHighlights(fromX, fromY, toX, toY);
4838         }
4839     }
4840
4841     /* Start the clocks */
4842     whiteFlag = blackFlag = FALSE;
4843     appData.clockMode = !(basetime == 0 && increment == 0);
4844     if (ticking == 0) {
4845       ics_clock_paused = TRUE;
4846       StopClocks();
4847     } else if (ticking == 1) {
4848       ics_clock_paused = FALSE;
4849     }
4850     if (gameMode == IcsIdle ||
4851         relation == RELATION_OBSERVING_STATIC ||
4852         relation == RELATION_EXAMINING ||
4853         ics_clock_paused)
4854       DisplayBothClocks();
4855     else
4856       StartClocks();
4857
4858     /* Display opponents and material strengths */
4859     if (gameInfo.variant != VariantBughouse &&
4860         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4861         if (tinyLayout || smallLayout) {
4862             if(gameInfo.variant == VariantNormal)
4863               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4864                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4865                     basetime, increment);
4866             else
4867               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4868                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4869                     basetime, increment, (int) gameInfo.variant);
4870         } else {
4871             if(gameInfo.variant == VariantNormal)
4872               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4873                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4874                     basetime, increment);
4875             else
4876               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4877                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4878                     basetime, increment, VariantName(gameInfo.variant));
4879         }
4880         DisplayTitle(str);
4881   if (appData.debugMode) {
4882     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4883   }
4884     }
4885
4886
4887     /* Display the board */
4888     if (!pausing && !appData.noGUI) {
4889
4890       if (appData.premove)
4891           if (!gotPremove ||
4892              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4893              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4894               ClearPremoveHighlights();
4895
4896       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4897         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4898       DrawPosition(j, boards[currentMove]);
4899
4900       DisplayMove(moveNum - 1);
4901       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4902             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4903               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4904         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4905       }
4906     }
4907
4908     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4909 #if ZIPPY
4910     if(bookHit) { // [HGM] book: simulate book reply
4911         static char bookMove[MSG_SIZ]; // a bit generous?
4912
4913         programStats.nodes = programStats.depth = programStats.time =
4914         programStats.score = programStats.got_only_move = 0;
4915         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4916
4917         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4918         strcat(bookMove, bookHit);
4919         HandleMachineMove(bookMove, &first);
4920     }
4921 #endif
4922 }
4923
4924 void
4925 GetMoveListEvent ()
4926 {
4927     char buf[MSG_SIZ];
4928     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4929         ics_getting_history = H_REQUESTED;
4930         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4931         SendToICS(buf);
4932     }
4933 }
4934
4935 void
4936 SendToBoth (char *msg)
4937 {   // to make it easy to keep two engines in step in dual analysis
4938     SendToProgram(msg, &first);
4939     if(second.analyzing) SendToProgram(msg, &second);
4940 }
4941
4942 void
4943 AnalysisPeriodicEvent (int force)
4944 {
4945     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4946          && !force) || !appData.periodicUpdates)
4947       return;
4948
4949     /* Send . command to Crafty to collect stats */
4950     SendToBoth(".\n");
4951
4952     /* Don't send another until we get a response (this makes
4953        us stop sending to old Crafty's which don't understand
4954        the "." command (sending illegal cmds resets node count & time,
4955        which looks bad)) */
4956     programStats.ok_to_send = 0;
4957 }
4958
4959 void
4960 ics_update_width (int new_width)
4961 {
4962         ics_printf("set width %d\n", new_width);
4963 }
4964
4965 void
4966 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4967 {
4968     char buf[MSG_SIZ];
4969
4970     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4971         // null move in variant where engine does not understand it (for analysis purposes)
4972         SendBoard(cps, moveNum + 1); // send position after move in stead.
4973         return;
4974     }
4975     if (cps->useUsermove) {
4976       SendToProgram("usermove ", cps);
4977     }
4978     if (cps->useSAN) {
4979       char *space;
4980       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4981         int len = space - parseList[moveNum];
4982         memcpy(buf, parseList[moveNum], len);
4983         buf[len++] = '\n';
4984         buf[len] = NULLCHAR;
4985       } else {
4986         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4987       }
4988       SendToProgram(buf, cps);
4989     } else {
4990       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4991         AlphaRank(moveList[moveNum], 4);
4992         SendToProgram(moveList[moveNum], cps);
4993         AlphaRank(moveList[moveNum], 4); // and back
4994       } else
4995       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4996        * the engine. It would be nice to have a better way to identify castle
4997        * moves here. */
4998       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4999                                                                          && cps->useOOCastle) {
5000         int fromX = moveList[moveNum][0] - AAA;
5001         int fromY = moveList[moveNum][1] - ONE;
5002         int toX = moveList[moveNum][2] - AAA;
5003         int toY = moveList[moveNum][3] - ONE;
5004         if((boards[moveNum][fromY][fromX] == WhiteKing
5005             && boards[moveNum][toY][toX] == WhiteRook)
5006            || (boards[moveNum][fromY][fromX] == BlackKing
5007                && boards[moveNum][toY][toX] == BlackRook)) {
5008           if(toX > fromX) SendToProgram("O-O\n", cps);
5009           else SendToProgram("O-O-O\n", cps);
5010         }
5011         else SendToProgram(moveList[moveNum], cps);
5012       } else
5013       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5014         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5015           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5016           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5017                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5018         } else
5019           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5020                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5021         SendToProgram(buf, cps);
5022       }
5023       else SendToProgram(moveList[moveNum], cps);
5024       /* End of additions by Tord */
5025     }
5026
5027     /* [HGM] setting up the opening has brought engine in force mode! */
5028     /*       Send 'go' if we are in a mode where machine should play. */
5029     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5030         (gameMode == TwoMachinesPlay   ||
5031 #if ZIPPY
5032          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5033 #endif
5034          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5035         SendToProgram("go\n", cps);
5036   if (appData.debugMode) {
5037     fprintf(debugFP, "(extra)\n");
5038   }
5039     }
5040     setboardSpoiledMachineBlack = 0;
5041 }
5042
5043 void
5044 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5045 {
5046     char user_move[MSG_SIZ];
5047     char suffix[4];
5048
5049     if(gameInfo.variant == VariantSChess && promoChar) {
5050         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5051         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5052     } else suffix[0] = NULLCHAR;
5053
5054     switch (moveType) {
5055       default:
5056         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5057                 (int)moveType, fromX, fromY, toX, toY);
5058         DisplayError(user_move + strlen("say "), 0);
5059         break;
5060       case WhiteKingSideCastle:
5061       case BlackKingSideCastle:
5062       case WhiteQueenSideCastleWild:
5063       case BlackQueenSideCastleWild:
5064       /* PUSH Fabien */
5065       case WhiteHSideCastleFR:
5066       case BlackHSideCastleFR:
5067       /* POP Fabien */
5068         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5069         break;
5070       case WhiteQueenSideCastle:
5071       case BlackQueenSideCastle:
5072       case WhiteKingSideCastleWild:
5073       case BlackKingSideCastleWild:
5074       /* PUSH Fabien */
5075       case WhiteASideCastleFR:
5076       case BlackASideCastleFR:
5077       /* POP Fabien */
5078         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5079         break;
5080       case WhiteNonPromotion:
5081       case BlackNonPromotion:
5082         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5083         break;
5084       case WhitePromotion:
5085       case BlackPromotion:
5086         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5087           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5089                 PieceToChar(WhiteFerz));
5090         else if(gameInfo.variant == VariantGreat)
5091           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5092                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5093                 PieceToChar(WhiteMan));
5094         else
5095           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5096                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5097                 promoChar);
5098         break;
5099       case WhiteDrop:
5100       case BlackDrop:
5101       drop:
5102         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5103                  ToUpper(PieceToChar((ChessSquare) fromX)),
5104                  AAA + toX, ONE + toY);
5105         break;
5106       case IllegalMove:  /* could be a variant we don't quite understand */
5107         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5108       case NormalMove:
5109       case WhiteCapturesEnPassant:
5110       case BlackCapturesEnPassant:
5111         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5112                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5113         break;
5114     }
5115     SendToICS(user_move);
5116     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5117         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5118 }
5119
5120 void
5121 UploadGameEvent ()
5122 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5123     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5124     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5125     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5126       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5127       return;
5128     }
5129     if(gameMode != IcsExamining) { // is this ever not the case?
5130         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5131
5132         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5133           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5134         } else { // on FICS we must first go to general examine mode
5135           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5136         }
5137         if(gameInfo.variant != VariantNormal) {
5138             // try figure out wild number, as xboard names are not always valid on ICS
5139             for(i=1; i<=36; i++) {
5140               snprintf(buf, MSG_SIZ, "wild/%d", i);
5141                 if(StringToVariant(buf) == gameInfo.variant) break;
5142             }
5143             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5144             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5145             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5146         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5147         SendToICS(ics_prefix);
5148         SendToICS(buf);
5149         if(startedFromSetupPosition || backwardMostMove != 0) {
5150           fen = PositionToFEN(backwardMostMove, NULL);
5151           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5152             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5153             SendToICS(buf);
5154           } else { // FICS: everything has to set by separate bsetup commands
5155             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5156             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5157             SendToICS(buf);
5158             if(!WhiteOnMove(backwardMostMove)) {
5159                 SendToICS("bsetup tomove black\n");
5160             }
5161             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5162             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5163             SendToICS(buf);
5164             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5165             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5166             SendToICS(buf);
5167             i = boards[backwardMostMove][EP_STATUS];
5168             if(i >= 0) { // set e.p.
5169               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5170                 SendToICS(buf);
5171             }
5172             bsetup++;
5173           }
5174         }
5175       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5176             SendToICS("bsetup done\n"); // switch to normal examining.
5177     }
5178     for(i = backwardMostMove; i<last; i++) {
5179         char buf[20];
5180         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5181         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5182             int len = strlen(moveList[i]);
5183             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5184             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5185         }
5186         SendToICS(buf);
5187     }
5188     SendToICS(ics_prefix);
5189     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5190 }
5191
5192 void
5193 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5194 {
5195     if (rf == DROP_RANK) {
5196       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5197       sprintf(move, "%c@%c%c\n",
5198                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5199     } else {
5200         if (promoChar == 'x' || promoChar == NULLCHAR) {
5201           sprintf(move, "%c%c%c%c\n",
5202                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5203         } else {
5204             sprintf(move, "%c%c%c%c%c\n",
5205                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5206         }
5207     }
5208 }
5209
5210 void
5211 ProcessICSInitScript (FILE *f)
5212 {
5213     char buf[MSG_SIZ];
5214
5215     while (fgets(buf, MSG_SIZ, f)) {
5216         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5217     }
5218
5219     fclose(f);
5220 }
5221
5222
5223 static int lastX, lastY, selectFlag, dragging;
5224
5225 void
5226 Sweep (int step)
5227 {
5228     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5229     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5230     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5231     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5232     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5233     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5234     do {
5235         promoSweep -= step;
5236         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5237         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5238         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5239         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5240         if(!step) step = -1;
5241     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5242             appData.testLegality && (promoSweep == king ||
5243             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5244     if(toX >= 0) {
5245         int victim = boards[currentMove][toY][toX];
5246         boards[currentMove][toY][toX] = promoSweep;
5247         DrawPosition(FALSE, boards[currentMove]);
5248         boards[currentMove][toY][toX] = victim;
5249     } else
5250     ChangeDragPiece(promoSweep);
5251 }
5252
5253 int
5254 PromoScroll (int x, int y)
5255 {
5256   int step = 0;
5257
5258   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5259   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5260   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5261   if(!step) return FALSE;
5262   lastX = x; lastY = y;
5263   if((promoSweep < BlackPawn) == flipView) step = -step;
5264   if(step > 0) selectFlag = 1;
5265   if(!selectFlag) Sweep(step);
5266   return FALSE;
5267 }
5268
5269 void
5270 NextPiece (int step)
5271 {
5272     ChessSquare piece = boards[currentMove][toY][toX];
5273     do {
5274         pieceSweep -= step;
5275         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5276         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5277         if(!step) step = -1;
5278     } while(PieceToChar(pieceSweep) == '.');
5279     boards[currentMove][toY][toX] = pieceSweep;
5280     DrawPosition(FALSE, boards[currentMove]);
5281     boards[currentMove][toY][toX] = piece;
5282 }
5283 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5284 void
5285 AlphaRank (char *move, int n)
5286 {
5287 //    char *p = move, c; int x, y;
5288
5289     if (appData.debugMode) {
5290         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5291     }
5292
5293     if(move[1]=='*' &&
5294        move[2]>='0' && move[2]<='9' &&
5295        move[3]>='a' && move[3]<='x'    ) {
5296         move[1] = '@';
5297         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5298         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5299     } else
5300     if(move[0]>='0' && move[0]<='9' &&
5301        move[1]>='a' && move[1]<='x' &&
5302        move[2]>='0' && move[2]<='9' &&
5303        move[3]>='a' && move[3]<='x'    ) {
5304         /* input move, Shogi -> normal */
5305         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5306         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5307         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5308         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5309     } else
5310     if(move[1]=='@' &&
5311        move[3]>='0' && move[3]<='9' &&
5312        move[2]>='a' && move[2]<='x'    ) {
5313         move[1] = '*';
5314         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5315         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5316     } else
5317     if(
5318        move[0]>='a' && move[0]<='x' &&
5319        move[3]>='0' && move[3]<='9' &&
5320        move[2]>='a' && move[2]<='x'    ) {
5321          /* output move, normal -> Shogi */
5322         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5323         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5324         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5325         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5326         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5327     }
5328     if (appData.debugMode) {
5329         fprintf(debugFP, "   out = '%s'\n", move);
5330     }
5331 }
5332
5333 char yy_textstr[8000];
5334
5335 /* Parser for moves from gnuchess, ICS, or user typein box */
5336 Boolean
5337 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5338 {
5339     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5340
5341     switch (*moveType) {
5342       case WhitePromotion:
5343       case BlackPromotion:
5344       case WhiteNonPromotion:
5345       case BlackNonPromotion:
5346       case NormalMove:
5347       case WhiteCapturesEnPassant:
5348       case BlackCapturesEnPassant:
5349       case WhiteKingSideCastle:
5350       case WhiteQueenSideCastle:
5351       case BlackKingSideCastle:
5352       case BlackQueenSideCastle:
5353       case WhiteKingSideCastleWild:
5354       case WhiteQueenSideCastleWild:
5355       case BlackKingSideCastleWild:
5356       case BlackQueenSideCastleWild:
5357       /* Code added by Tord: */
5358       case WhiteHSideCastleFR:
5359       case WhiteASideCastleFR:
5360       case BlackHSideCastleFR:
5361       case BlackASideCastleFR:
5362       /* End of code added by Tord */
5363       case IllegalMove:         /* bug or odd chess variant */
5364         *fromX = currentMoveString[0] - AAA;
5365         *fromY = currentMoveString[1] - ONE;
5366         *toX = currentMoveString[2] - AAA;
5367         *toY = currentMoveString[3] - ONE;
5368         *promoChar = currentMoveString[4];
5369         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5370             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5371     if (appData.debugMode) {
5372         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5373     }
5374             *fromX = *fromY = *toX = *toY = 0;
5375             return FALSE;
5376         }
5377         if (appData.testLegality) {
5378           return (*moveType != IllegalMove);
5379         } else {
5380           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5381                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5382         }
5383
5384       case WhiteDrop:
5385       case BlackDrop:
5386         *fromX = *moveType == WhiteDrop ?
5387           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5388           (int) CharToPiece(ToLower(currentMoveString[0]));
5389         *fromY = DROP_RANK;
5390         *toX = currentMoveString[2] - AAA;
5391         *toY = currentMoveString[3] - ONE;
5392         *promoChar = NULLCHAR;
5393         return TRUE;
5394
5395       case AmbiguousMove:
5396       case ImpossibleMove:
5397       case EndOfFile:
5398       case ElapsedTime:
5399       case Comment:
5400       case PGNTag:
5401       case NAG:
5402       case WhiteWins:
5403       case BlackWins:
5404       case GameIsDrawn:
5405       default:
5406     if (appData.debugMode) {
5407         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5408     }
5409         /* bug? */
5410         *fromX = *fromY = *toX = *toY = 0;
5411         *promoChar = NULLCHAR;
5412         return FALSE;
5413     }
5414 }
5415
5416 Boolean pushed = FALSE;
5417 char *lastParseAttempt;
5418
5419 void
5420 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5421 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5422   int fromX, fromY, toX, toY; char promoChar;
5423   ChessMove moveType;
5424   Boolean valid;
5425   int nr = 0;
5426
5427   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5428   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5429     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5430     pushed = TRUE;
5431   }
5432   endPV = forwardMostMove;
5433   do {
5434     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5435     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5436     lastParseAttempt = pv;
5437     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5438     if(!valid && nr == 0 &&
5439        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5440         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5441         // Hande case where played move is different from leading PV move
5442         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5443         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5444         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5445         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5446           endPV += 2; // if position different, keep this
5447           moveList[endPV-1][0] = fromX + AAA;
5448           moveList[endPV-1][1] = fromY + ONE;
5449           moveList[endPV-1][2] = toX + AAA;
5450           moveList[endPV-1][3] = toY + ONE;
5451           parseList[endPV-1][0] = NULLCHAR;
5452           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5453         }
5454       }
5455     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5456     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5457     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5458     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5459         valid++; // allow comments in PV
5460         continue;
5461     }
5462     nr++;
5463     if(endPV+1 > framePtr) break; // no space, truncate
5464     if(!valid) break;
5465     endPV++;
5466     CopyBoard(boards[endPV], boards[endPV-1]);
5467     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5468     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5469     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5470     CoordsToAlgebraic(boards[endPV - 1],
5471                              PosFlags(endPV - 1),
5472                              fromY, fromX, toY, toX, promoChar,
5473                              parseList[endPV - 1]);
5474   } while(valid);
5475   if(atEnd == 2) return; // used hidden, for PV conversion
5476   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5477   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5478   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5479                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5480   DrawPosition(TRUE, boards[currentMove]);
5481 }
5482
5483 int
5484 MultiPV (ChessProgramState *cps)
5485 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5486         int i;
5487         for(i=0; i<cps->nrOptions; i++)
5488             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5489                 return i;
5490         return -1;
5491 }
5492
5493 Boolean
5494 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5495 {
5496         int startPV, multi, lineStart, origIndex = index;
5497         char *p, buf2[MSG_SIZ];
5498         ChessProgramState *cps = (pane ? &second : &first);
5499
5500         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5501         lastX = x; lastY = y;
5502         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5503         lineStart = startPV = index;
5504         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5505         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5506         index = startPV;
5507         do{ while(buf[index] && buf[index] != '\n') index++;
5508         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5509         buf[index] = 0;
5510         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5511                 int n = cps->option[multi].value;
5512                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5513                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5514                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5515                 cps->option[multi].value = n;
5516                 *start = *end = 0;
5517                 return FALSE;
5518         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5519                 ExcludeClick(origIndex - lineStart);
5520                 return FALSE;
5521         }
5522         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5523         *start = startPV; *end = index-1;
5524         return TRUE;
5525 }
5526
5527 char *
5528 PvToSAN (char *pv)
5529 {
5530         static char buf[10*MSG_SIZ];
5531         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5532         *buf = NULLCHAR;
5533         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5534         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5535         for(i = forwardMostMove; i<endPV; i++){
5536             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5537             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5538             k += strlen(buf+k);
5539         }
5540         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5541         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5542         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5543         endPV = savedEnd;
5544         return buf;
5545 }
5546
5547 Boolean
5548 LoadPV (int x, int y)
5549 { // called on right mouse click to load PV
5550   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5551   lastX = x; lastY = y;
5552   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5553   return TRUE;
5554 }
5555
5556 void
5557 UnLoadPV ()
5558 {
5559   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5560   if(endPV < 0) return;
5561   if(appData.autoCopyPV) CopyFENToClipboard();
5562   endPV = -1;
5563   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5564         Boolean saveAnimate = appData.animate;
5565         if(pushed) {
5566             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5567                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5568             } else storedGames--; // abandon shelved tail of original game
5569         }
5570         pushed = FALSE;
5571         forwardMostMove = currentMove;
5572         currentMove = oldFMM;
5573         appData.animate = FALSE;
5574         ToNrEvent(forwardMostMove);
5575         appData.animate = saveAnimate;
5576   }
5577   currentMove = forwardMostMove;
5578   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5579   ClearPremoveHighlights();
5580   DrawPosition(TRUE, boards[currentMove]);
5581 }
5582
5583 void
5584 MovePV (int x, int y, int h)
5585 { // step through PV based on mouse coordinates (called on mouse move)
5586   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5587
5588   // we must somehow check if right button is still down (might be released off board!)
5589   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5590   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5591   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5592   if(!step) return;
5593   lastX = x; lastY = y;
5594
5595   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5596   if(endPV < 0) return;
5597   if(y < margin) step = 1; else
5598   if(y > h - margin) step = -1;
5599   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5600   currentMove += step;
5601   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5602   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5603                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5604   DrawPosition(FALSE, boards[currentMove]);
5605 }
5606
5607
5608 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5609 // All positions will have equal probability, but the current method will not provide a unique
5610 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5611 #define DARK 1
5612 #define LITE 2
5613 #define ANY 3
5614
5615 int squaresLeft[4];
5616 int piecesLeft[(int)BlackPawn];
5617 int seed, nrOfShuffles;
5618
5619 void
5620 GetPositionNumber ()
5621 {       // sets global variable seed
5622         int i;
5623
5624         seed = appData.defaultFrcPosition;
5625         if(seed < 0) { // randomize based on time for negative FRC position numbers
5626                 for(i=0; i<50; i++) seed += random();
5627                 seed = random() ^ random() >> 8 ^ random() << 8;
5628                 if(seed<0) seed = -seed;
5629         }
5630 }
5631
5632 int
5633 put (Board board, int pieceType, int rank, int n, int shade)
5634 // put the piece on the (n-1)-th empty squares of the given shade
5635 {
5636         int i;
5637
5638         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5639                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5640                         board[rank][i] = (ChessSquare) pieceType;
5641                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5642                         squaresLeft[ANY]--;
5643                         piecesLeft[pieceType]--;
5644                         return i;
5645                 }
5646         }
5647         return -1;
5648 }
5649
5650
5651 void
5652 AddOnePiece (Board board, int pieceType, int rank, int shade)
5653 // calculate where the next piece goes, (any empty square), and put it there
5654 {
5655         int i;
5656
5657         i = seed % squaresLeft[shade];
5658         nrOfShuffles *= squaresLeft[shade];
5659         seed /= squaresLeft[shade];
5660         put(board, pieceType, rank, i, shade);
5661 }
5662
5663 void
5664 AddTwoPieces (Board board, int pieceType, int rank)
5665 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5666 {
5667         int i, n=squaresLeft[ANY], j=n-1, k;
5668
5669         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5670         i = seed % k;  // pick one
5671         nrOfShuffles *= k;
5672         seed /= k;
5673         while(i >= j) i -= j--;
5674         j = n - 1 - j; i += j;
5675         put(board, pieceType, rank, j, ANY);
5676         put(board, pieceType, rank, i, ANY);
5677 }
5678
5679 void
5680 SetUpShuffle (Board board, int number)
5681 {
5682         int i, p, first=1;
5683
5684         GetPositionNumber(); nrOfShuffles = 1;
5685
5686         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5687         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5688         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5689
5690         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5691
5692         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5693             p = (int) board[0][i];
5694             if(p < (int) BlackPawn) piecesLeft[p] ++;
5695             board[0][i] = EmptySquare;
5696         }
5697
5698         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5699             // shuffles restricted to allow normal castling put KRR first
5700             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5701                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5702             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5703                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5704             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5705                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5706             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5707                 put(board, WhiteRook, 0, 0, ANY);
5708             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5709         }
5710
5711         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5712             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5713             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5714                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5715                 while(piecesLeft[p] >= 2) {
5716                     AddOnePiece(board, p, 0, LITE);
5717                     AddOnePiece(board, p, 0, DARK);
5718                 }
5719                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5720             }
5721
5722         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5723             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5724             // but we leave King and Rooks for last, to possibly obey FRC restriction
5725             if(p == (int)WhiteRook) continue;
5726             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5727             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5728         }
5729
5730         // now everything is placed, except perhaps King (Unicorn) and Rooks
5731
5732         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5733             // Last King gets castling rights
5734             while(piecesLeft[(int)WhiteUnicorn]) {
5735                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5736                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5737             }
5738
5739             while(piecesLeft[(int)WhiteKing]) {
5740                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5741                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5742             }
5743
5744
5745         } else {
5746             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5747             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5748         }
5749
5750         // Only Rooks can be left; simply place them all
5751         while(piecesLeft[(int)WhiteRook]) {
5752                 i = put(board, WhiteRook, 0, 0, ANY);
5753                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5754                         if(first) {
5755                                 first=0;
5756                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5757                         }
5758                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5759                 }
5760         }
5761         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5762             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5763         }
5764
5765         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5766 }
5767
5768 int
5769 SetCharTable (char *table, const char * map)
5770 /* [HGM] moved here from winboard.c because of its general usefulness */
5771 /*       Basically a safe strcpy that uses the last character as King */
5772 {
5773     int result = FALSE; int NrPieces;
5774
5775     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5776                     && NrPieces >= 12 && !(NrPieces&1)) {
5777         int i; /* [HGM] Accept even length from 12 to 34 */
5778
5779         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5780         for( i=0; i<NrPieces/2-1; i++ ) {
5781             table[i] = map[i];
5782             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5783         }
5784         table[(int) WhiteKing]  = map[NrPieces/2-1];
5785         table[(int) BlackKing]  = map[NrPieces-1];
5786
5787         result = TRUE;
5788     }
5789
5790     return result;
5791 }
5792
5793 void
5794 Prelude (Board board)
5795 {       // [HGM] superchess: random selection of exo-pieces
5796         int i, j, k; ChessSquare p;
5797         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5798
5799         GetPositionNumber(); // use FRC position number
5800
5801         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5802             SetCharTable(pieceToChar, appData.pieceToCharTable);
5803             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5804                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5805         }
5806
5807         j = seed%4;                 seed /= 4;
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 >= j); seed /= 3;
5812         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3;
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%2 + (seed%2 >= j); seed /= 2;
5820         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5821         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5822         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5823         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5824         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5825         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5826         put(board, exoPieces[0],    0, 0, ANY);
5827         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5828 }
5829
5830 void
5831 InitPosition (int redraw)
5832 {
5833     ChessSquare (* pieces)[BOARD_FILES];
5834     int i, j, pawnRow, overrule,
5835     oldx = gameInfo.boardWidth,
5836     oldy = gameInfo.boardHeight,
5837     oldh = gameInfo.holdingsWidth;
5838     static int oldv;
5839
5840     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5841
5842     /* [AS] Initialize pv info list [HGM] and game status */
5843     {
5844         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5845             pvInfoList[i].depth = 0;
5846             boards[i][EP_STATUS] = EP_NONE;
5847             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5848         }
5849
5850         initialRulePlies = 0; /* 50-move counter start */
5851
5852         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5853         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5854     }
5855
5856
5857     /* [HGM] logic here is completely changed. In stead of full positions */
5858     /* the initialized data only consist of the two backranks. The switch */
5859     /* selects which one we will use, which is than copied to the Board   */
5860     /* initialPosition, which for the rest is initialized by Pawns and    */
5861     /* empty squares. This initial position is then copied to boards[0],  */
5862     /* possibly after shuffling, so that it remains available.            */
5863
5864     gameInfo.holdingsWidth = 0; /* default board sizes */
5865     gameInfo.boardWidth    = 8;
5866     gameInfo.boardHeight   = 8;
5867     gameInfo.holdingsSize  = 0;
5868     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5869     for(i=0; i<BOARD_FILES-2; i++)
5870       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5871     initialPosition[EP_STATUS] = EP_NONE;
5872     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5873     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5874          SetCharTable(pieceNickName, appData.pieceNickNames);
5875     else SetCharTable(pieceNickName, "............");
5876     pieces = FIDEArray;
5877
5878     switch (gameInfo.variant) {
5879     case VariantFischeRandom:
5880       shuffleOpenings = TRUE;
5881     default:
5882       break;
5883     case VariantShatranj:
5884       pieces = ShatranjArray;
5885       nrCastlingRights = 0;
5886       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5887       break;
5888     case VariantMakruk:
5889       pieces = makrukArray;
5890       nrCastlingRights = 0;
5891       startedFromSetupPosition = TRUE;
5892       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5893       break;
5894     case VariantTwoKings:
5895       pieces = twoKingsArray;
5896       break;
5897     case VariantGrand:
5898       pieces = GrandArray;
5899       nrCastlingRights = 0;
5900       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5901       gameInfo.boardWidth = 10;
5902       gameInfo.boardHeight = 10;
5903       gameInfo.holdingsSize = 7;
5904       break;
5905     case VariantCapaRandom:
5906       shuffleOpenings = TRUE;
5907     case VariantCapablanca:
5908       pieces = CapablancaArray;
5909       gameInfo.boardWidth = 10;
5910       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5911       break;
5912     case VariantGothic:
5913       pieces = GothicArray;
5914       gameInfo.boardWidth = 10;
5915       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5916       break;
5917     case VariantSChess:
5918       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5919       gameInfo.holdingsSize = 7;
5920       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5921       break;
5922     case VariantJanus:
5923       pieces = JanusArray;
5924       gameInfo.boardWidth = 10;
5925       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5926       nrCastlingRights = 6;
5927         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5928         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5929         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5930         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5931         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5932         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5933       break;
5934     case VariantFalcon:
5935       pieces = FalconArray;
5936       gameInfo.boardWidth = 10;
5937       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5938       break;
5939     case VariantXiangqi:
5940       pieces = XiangqiArray;
5941       gameInfo.boardWidth  = 9;
5942       gameInfo.boardHeight = 10;
5943       nrCastlingRights = 0;
5944       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5945       break;
5946     case VariantShogi:
5947       pieces = ShogiArray;
5948       gameInfo.boardWidth  = 9;
5949       gameInfo.boardHeight = 9;
5950       gameInfo.holdingsSize = 7;
5951       nrCastlingRights = 0;
5952       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5953       break;
5954     case VariantCourier:
5955       pieces = CourierArray;
5956       gameInfo.boardWidth  = 12;
5957       nrCastlingRights = 0;
5958       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5959       break;
5960     case VariantKnightmate:
5961       pieces = KnightmateArray;
5962       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5963       break;
5964     case VariantSpartan:
5965       pieces = SpartanArray;
5966       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5967       break;
5968     case VariantFairy:
5969       pieces = fairyArray;
5970       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5971       break;
5972     case VariantGreat:
5973       pieces = GreatArray;
5974       gameInfo.boardWidth = 10;
5975       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5976       gameInfo.holdingsSize = 8;
5977       break;
5978     case VariantSuper:
5979       pieces = FIDEArray;
5980       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5981       gameInfo.holdingsSize = 8;
5982       startedFromSetupPosition = TRUE;
5983       break;
5984     case VariantCrazyhouse:
5985     case VariantBughouse:
5986       pieces = FIDEArray;
5987       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5988       gameInfo.holdingsSize = 5;
5989       break;
5990     case VariantWildCastle:
5991       pieces = FIDEArray;
5992       /* !!?shuffle with kings guaranteed to be on d or e file */
5993       shuffleOpenings = 1;
5994       break;
5995     case VariantNoCastle:
5996       pieces = FIDEArray;
5997       nrCastlingRights = 0;
5998       /* !!?unconstrained back-rank shuffle */
5999       shuffleOpenings = 1;
6000       break;
6001     }
6002
6003     overrule = 0;
6004     if(appData.NrFiles >= 0) {
6005         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6006         gameInfo.boardWidth = appData.NrFiles;
6007     }
6008     if(appData.NrRanks >= 0) {
6009         gameInfo.boardHeight = appData.NrRanks;
6010     }
6011     if(appData.holdingsSize >= 0) {
6012         i = appData.holdingsSize;
6013         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6014         gameInfo.holdingsSize = i;
6015     }
6016     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6017     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6018         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6019
6020     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6021     if(pawnRow < 1) pawnRow = 1;
6022     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6023
6024     /* User pieceToChar list overrules defaults */
6025     if(appData.pieceToCharTable != NULL)
6026         SetCharTable(pieceToChar, appData.pieceToCharTable);
6027
6028     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6029
6030         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6031             s = (ChessSquare) 0; /* account holding counts in guard band */
6032         for( i=0; i<BOARD_HEIGHT; i++ )
6033             initialPosition[i][j] = s;
6034
6035         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6036         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6037         initialPosition[pawnRow][j] = WhitePawn;
6038         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6039         if(gameInfo.variant == VariantXiangqi) {
6040             if(j&1) {
6041                 initialPosition[pawnRow][j] =
6042                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6043                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6044                    initialPosition[2][j] = WhiteCannon;
6045                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6046                 }
6047             }
6048         }
6049         if(gameInfo.variant == VariantGrand) {
6050             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6051                initialPosition[0][j] = WhiteRook;
6052                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6053             }
6054         }
6055         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6056     }
6057     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6058
6059             j=BOARD_LEFT+1;
6060             initialPosition[1][j] = WhiteBishop;
6061             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6062             j=BOARD_RGHT-2;
6063             initialPosition[1][j] = WhiteRook;
6064             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6065     }
6066
6067     if( nrCastlingRights == -1) {
6068         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6069         /*       This sets default castling rights from none to normal corners   */
6070         /* Variants with other castling rights must set them themselves above    */
6071         nrCastlingRights = 6;
6072
6073         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6074         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6075         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6076         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6077         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6078         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6079      }
6080
6081      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6082      if(gameInfo.variant == VariantGreat) { // promotion commoners
6083         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6084         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6085         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6086         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6087      }
6088      if( gameInfo.variant == VariantSChess ) {
6089       initialPosition[1][0] = BlackMarshall;
6090       initialPosition[2][0] = BlackAngel;
6091       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6092       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6093       initialPosition[1][1] = initialPosition[2][1] =
6094       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6095      }
6096   if (appData.debugMode) {
6097     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6098   }
6099     if(shuffleOpenings) {
6100         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6101         startedFromSetupPosition = TRUE;
6102     }
6103     if(startedFromPositionFile) {
6104       /* [HGM] loadPos: use PositionFile for every new game */
6105       CopyBoard(initialPosition, filePosition);
6106       for(i=0; i<nrCastlingRights; i++)
6107           initialRights[i] = filePosition[CASTLING][i];
6108       startedFromSetupPosition = TRUE;
6109     }
6110
6111     CopyBoard(boards[0], initialPosition);
6112
6113     if(oldx != gameInfo.boardWidth ||
6114        oldy != gameInfo.boardHeight ||
6115        oldv != gameInfo.variant ||
6116        oldh != gameInfo.holdingsWidth
6117                                          )
6118             InitDrawingSizes(-2 ,0);
6119
6120     oldv = gameInfo.variant;
6121     if (redraw)
6122       DrawPosition(TRUE, boards[currentMove]);
6123 }
6124
6125 void
6126 SendBoard (ChessProgramState *cps, int moveNum)
6127 {
6128     char message[MSG_SIZ];
6129
6130     if (cps->useSetboard) {
6131       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6132       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6133       SendToProgram(message, cps);
6134       free(fen);
6135
6136     } else {
6137       ChessSquare *bp;
6138       int i, j, left=0, right=BOARD_WIDTH;
6139       /* Kludge to set black to move, avoiding the troublesome and now
6140        * deprecated "black" command.
6141        */
6142       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6143         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6144
6145       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6146
6147       SendToProgram("edit\n", cps);
6148       SendToProgram("#\n", cps);
6149       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6150         bp = &boards[moveNum][i][left];
6151         for (j = left; j < right; j++, bp++) {
6152           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6153           if ((int) *bp < (int) BlackPawn) {
6154             if(j == BOARD_RGHT+1)
6155                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6156             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6157             if(message[0] == '+' || message[0] == '~') {
6158               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6159                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6160                         AAA + j, ONE + i);
6161             }
6162             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6163                 message[1] = BOARD_RGHT   - 1 - j + '1';
6164                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6165             }
6166             SendToProgram(message, cps);
6167           }
6168         }
6169       }
6170
6171       SendToProgram("c\n", cps);
6172       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6173         bp = &boards[moveNum][i][left];
6174         for (j = left; j < right; j++, bp++) {
6175           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6176           if (((int) *bp != (int) EmptySquare)
6177               && ((int) *bp >= (int) BlackPawn)) {
6178             if(j == BOARD_LEFT-2)
6179                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6180             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6181                     AAA + j, ONE + i);
6182             if(message[0] == '+' || message[0] == '~') {
6183               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6184                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6185                         AAA + j, ONE + i);
6186             }
6187             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6188                 message[1] = BOARD_RGHT   - 1 - j + '1';
6189                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6190             }
6191             SendToProgram(message, cps);
6192           }
6193         }
6194       }
6195
6196       SendToProgram(".\n", cps);
6197     }
6198     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6199 }
6200
6201 char exclusionHeader[MSG_SIZ];
6202 int exCnt, excludePtr;
6203 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6204 static Exclusion excluTab[200];
6205 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6206
6207 static void
6208 WriteMap (int s)
6209 {
6210     int j;
6211     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6212     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6213 }
6214
6215 static void
6216 ClearMap ()
6217 {
6218     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6219     excludePtr = 24; exCnt = 0;
6220     WriteMap(0);
6221 }
6222
6223 static void
6224 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6225 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6226     char buf[2*MOVE_LEN], *p;
6227     Exclusion *e = excluTab;
6228     int i;
6229     for(i=0; i<exCnt; i++)
6230         if(e[i].ff == fromX && e[i].fr == fromY &&
6231            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6232     if(i == exCnt) { // was not in exclude list; add it
6233         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6234         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6235             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6236             return; // abort
6237         }
6238         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6239         excludePtr++; e[i].mark = excludePtr++;
6240         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6241         exCnt++;
6242     }
6243     exclusionHeader[e[i].mark] = state;
6244 }
6245
6246 static int
6247 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6248 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6249     char buf[MSG_SIZ];
6250     int j, k;
6251     ChessMove moveType;
6252     if((signed char)promoChar == -1) { // kludge to indicate best move
6253         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6254             return 1; // if unparsable, abort
6255     }
6256     // update exclusion map (resolving toggle by consulting existing state)
6257     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6258     j = k%8; k >>= 3;
6259     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6260     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6261          excludeMap[k] |=   1<<j;
6262     else excludeMap[k] &= ~(1<<j);
6263     // update header
6264     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6265     // inform engine
6266     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6267     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6268     SendToBoth(buf);
6269     return (state == '+');
6270 }
6271
6272 static void
6273 ExcludeClick (int index)
6274 {
6275     int i, j;
6276     Exclusion *e = excluTab;
6277     if(index < 25) { // none, best or tail clicked
6278         if(index < 13) { // none: include all
6279             WriteMap(0); // clear map
6280             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6281             SendToBoth("include all\n"); // and inform engine
6282         } else if(index > 18) { // tail
6283             if(exclusionHeader[19] == '-') { // tail was excluded
6284                 SendToBoth("include all\n");
6285                 WriteMap(0); // clear map completely
6286                 // now re-exclude selected moves
6287                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6288                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6289             } else { // tail was included or in mixed state
6290                 SendToBoth("exclude all\n");
6291                 WriteMap(0xFF); // fill map completely
6292                 // now re-include selected moves
6293                 j = 0; // count them
6294                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6295                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6296                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6297             }
6298         } else { // best
6299             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6300         }
6301     } else {
6302         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6303             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6304             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6305             break;
6306         }
6307     }
6308 }
6309
6310 ChessSquare
6311 DefaultPromoChoice (int white)
6312 {
6313     ChessSquare result;
6314     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6315         result = WhiteFerz; // no choice
6316     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6317         result= WhiteKing; // in Suicide Q is the last thing we want
6318     else if(gameInfo.variant == VariantSpartan)
6319         result = white ? WhiteQueen : WhiteAngel;
6320     else result = WhiteQueen;
6321     if(!white) result = WHITE_TO_BLACK result;
6322     return result;
6323 }
6324
6325 static int autoQueen; // [HGM] oneclick
6326
6327 int
6328 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6329 {
6330     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6331     /* [HGM] add Shogi promotions */
6332     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6333     ChessSquare piece;
6334     ChessMove moveType;
6335     Boolean premove;
6336
6337     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6338     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6339
6340     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6341       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6342         return FALSE;
6343
6344     piece = boards[currentMove][fromY][fromX];
6345     if(gameInfo.variant == VariantShogi) {
6346         promotionZoneSize = BOARD_HEIGHT/3;
6347         highestPromotingPiece = (int)WhiteFerz;
6348     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6349         promotionZoneSize = 3;
6350     }
6351
6352     // Treat Lance as Pawn when it is not representing Amazon
6353     if(gameInfo.variant != VariantSuper) {
6354         if(piece == WhiteLance) piece = WhitePawn; else
6355         if(piece == BlackLance) piece = BlackPawn;
6356     }
6357
6358     // next weed out all moves that do not touch the promotion zone at all
6359     if((int)piece >= BlackPawn) {
6360         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6361              return FALSE;
6362         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6363     } else {
6364         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6365            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6366     }
6367
6368     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6369
6370     // weed out mandatory Shogi promotions
6371     if(gameInfo.variant == VariantShogi) {
6372         if(piece >= BlackPawn) {
6373             if(toY == 0 && piece == BlackPawn ||
6374                toY == 0 && piece == BlackQueen ||
6375                toY <= 1 && piece == BlackKnight) {
6376                 *promoChoice = '+';
6377                 return FALSE;
6378             }
6379         } else {
6380             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6381                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6382                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6383                 *promoChoice = '+';
6384                 return FALSE;
6385             }
6386         }
6387     }
6388
6389     // weed out obviously illegal Pawn moves
6390     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6391         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6392         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6393         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6394         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6395         // note we are not allowed to test for valid (non-)capture, due to premove
6396     }
6397
6398     // we either have a choice what to promote to, or (in Shogi) whether to promote
6399     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6400         *promoChoice = PieceToChar(BlackFerz);  // no choice
6401         return FALSE;
6402     }
6403     // no sense asking what we must promote to if it is going to explode...
6404     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6405         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6406         return FALSE;
6407     }
6408     // give caller the default choice even if we will not make it
6409     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6410     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6411     if(        sweepSelect && gameInfo.variant != VariantGreat
6412                            && gameInfo.variant != VariantGrand
6413                            && gameInfo.variant != VariantSuper) return FALSE;
6414     if(autoQueen) return FALSE; // predetermined
6415
6416     // suppress promotion popup on illegal moves that are not premoves
6417     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6418               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6419     if(appData.testLegality && !premove) {
6420         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6421                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6422         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6423             return FALSE;
6424     }
6425
6426     return TRUE;
6427 }
6428
6429 int
6430 InPalace (int row, int column)
6431 {   /* [HGM] for Xiangqi */
6432     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6433          column < (BOARD_WIDTH + 4)/2 &&
6434          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6435     return FALSE;
6436 }
6437
6438 int
6439 PieceForSquare (int x, int y)
6440 {
6441   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6442      return -1;
6443   else
6444      return boards[currentMove][y][x];
6445 }
6446
6447 int
6448 OKToStartUserMove (int x, int y)
6449 {
6450     ChessSquare from_piece;
6451     int white_piece;
6452
6453     if (matchMode) return FALSE;
6454     if (gameMode == EditPosition) return TRUE;
6455
6456     if (x >= 0 && y >= 0)
6457       from_piece = boards[currentMove][y][x];
6458     else
6459       from_piece = EmptySquare;
6460
6461     if (from_piece == EmptySquare) return FALSE;
6462
6463     white_piece = (int)from_piece >= (int)WhitePawn &&
6464       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6465
6466     switch (gameMode) {
6467       case AnalyzeFile:
6468       case TwoMachinesPlay:
6469       case EndOfGame:
6470         return FALSE;
6471
6472       case IcsObserving:
6473       case IcsIdle:
6474         return FALSE;
6475
6476       case MachinePlaysWhite:
6477       case IcsPlayingBlack:
6478         if (appData.zippyPlay) return FALSE;
6479         if (white_piece) {
6480             DisplayMoveError(_("You are playing Black"));
6481             return FALSE;
6482         }
6483         break;
6484
6485       case MachinePlaysBlack:
6486       case IcsPlayingWhite:
6487         if (appData.zippyPlay) return FALSE;
6488         if (!white_piece) {
6489             DisplayMoveError(_("You are playing White"));
6490             return FALSE;
6491         }
6492         break;
6493
6494       case PlayFromGameFile:
6495             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6496       case EditGame:
6497         if (!white_piece && WhiteOnMove(currentMove)) {
6498             DisplayMoveError(_("It is White's turn"));
6499             return FALSE;
6500         }
6501         if (white_piece && !WhiteOnMove(currentMove)) {
6502             DisplayMoveError(_("It is Black's turn"));
6503             return FALSE;
6504         }
6505         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6506             /* Editing correspondence game history */
6507             /* Could disallow this or prompt for confirmation */
6508             cmailOldMove = -1;
6509         }
6510         break;
6511
6512       case BeginningOfGame:
6513         if (appData.icsActive) return FALSE;
6514         if (!appData.noChessProgram) {
6515             if (!white_piece) {
6516                 DisplayMoveError(_("You are playing White"));
6517                 return FALSE;
6518             }
6519         }
6520         break;
6521
6522       case Training:
6523         if (!white_piece && WhiteOnMove(currentMove)) {
6524             DisplayMoveError(_("It is White's turn"));
6525             return FALSE;
6526         }
6527         if (white_piece && !WhiteOnMove(currentMove)) {
6528             DisplayMoveError(_("It is Black's turn"));
6529             return FALSE;
6530         }
6531         break;
6532
6533       default:
6534       case IcsExamining:
6535         break;
6536     }
6537     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6538         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6539         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6540         && gameMode != AnalyzeFile && gameMode != Training) {
6541         DisplayMoveError(_("Displayed position is not current"));
6542         return FALSE;
6543     }
6544     return TRUE;
6545 }
6546
6547 Boolean
6548 OnlyMove (int *x, int *y, Boolean captures)
6549 {
6550     DisambiguateClosure cl;
6551     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6552     switch(gameMode) {
6553       case MachinePlaysBlack:
6554       case IcsPlayingWhite:
6555       case BeginningOfGame:
6556         if(!WhiteOnMove(currentMove)) return FALSE;
6557         break;
6558       case MachinePlaysWhite:
6559       case IcsPlayingBlack:
6560         if(WhiteOnMove(currentMove)) return FALSE;
6561         break;
6562       case EditGame:
6563         break;
6564       default:
6565         return FALSE;
6566     }
6567     cl.pieceIn = EmptySquare;
6568     cl.rfIn = *y;
6569     cl.ffIn = *x;
6570     cl.rtIn = -1;
6571     cl.ftIn = -1;
6572     cl.promoCharIn = NULLCHAR;
6573     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6574     if( cl.kind == NormalMove ||
6575         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6576         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6577         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6578       fromX = cl.ff;
6579       fromY = cl.rf;
6580       *x = cl.ft;
6581       *y = cl.rt;
6582       return TRUE;
6583     }
6584     if(cl.kind != ImpossibleMove) return FALSE;
6585     cl.pieceIn = EmptySquare;
6586     cl.rfIn = -1;
6587     cl.ffIn = -1;
6588     cl.rtIn = *y;
6589     cl.ftIn = *x;
6590     cl.promoCharIn = NULLCHAR;
6591     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6592     if( cl.kind == NormalMove ||
6593         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6594         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6595         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6596       fromX = cl.ff;
6597       fromY = cl.rf;
6598       *x = cl.ft;
6599       *y = cl.rt;
6600       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6601       return TRUE;
6602     }
6603     return FALSE;
6604 }
6605
6606 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6607 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6608 int lastLoadGameUseList = FALSE;
6609 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6610 ChessMove lastLoadGameStart = EndOfFile;
6611 int doubleClick;
6612
6613 void
6614 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6615 {
6616     ChessMove moveType;
6617     ChessSquare pup;
6618     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6619
6620     /* Check if the user is playing in turn.  This is complicated because we
6621        let the user "pick up" a piece before it is his turn.  So the piece he
6622        tried to pick up may have been captured by the time he puts it down!
6623        Therefore we use the color the user is supposed to be playing in this
6624        test, not the color of the piece that is currently on the starting
6625        square---except in EditGame mode, where the user is playing both
6626        sides; fortunately there the capture race can't happen.  (It can
6627        now happen in IcsExamining mode, but that's just too bad.  The user
6628        will get a somewhat confusing message in that case.)
6629        */
6630
6631     switch (gameMode) {
6632       case AnalyzeFile:
6633       case TwoMachinesPlay:
6634       case EndOfGame:
6635       case IcsObserving:
6636       case IcsIdle:
6637         /* We switched into a game mode where moves are not accepted,
6638            perhaps while the mouse button was down. */
6639         return;
6640
6641       case MachinePlaysWhite:
6642         /* User is moving for Black */
6643         if (WhiteOnMove(currentMove)) {
6644             DisplayMoveError(_("It is White's turn"));
6645             return;
6646         }
6647         break;
6648
6649       case MachinePlaysBlack:
6650         /* User is moving for White */
6651         if (!WhiteOnMove(currentMove)) {
6652             DisplayMoveError(_("It is Black's turn"));
6653             return;
6654         }
6655         break;
6656
6657       case PlayFromGameFile:
6658             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6659       case EditGame:
6660       case IcsExamining:
6661       case BeginningOfGame:
6662       case AnalyzeMode:
6663       case Training:
6664         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6665         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6666             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6667             /* User is moving for Black */
6668             if (WhiteOnMove(currentMove)) {
6669                 DisplayMoveError(_("It is White's turn"));
6670                 return;
6671             }
6672         } else {
6673             /* User is moving for White */
6674             if (!WhiteOnMove(currentMove)) {
6675                 DisplayMoveError(_("It is Black's turn"));
6676                 return;
6677             }
6678         }
6679         break;
6680
6681       case IcsPlayingBlack:
6682         /* User is moving for Black */
6683         if (WhiteOnMove(currentMove)) {
6684             if (!appData.premove) {
6685                 DisplayMoveError(_("It is White's turn"));
6686             } else if (toX >= 0 && toY >= 0) {
6687                 premoveToX = toX;
6688                 premoveToY = toY;
6689                 premoveFromX = fromX;
6690                 premoveFromY = fromY;
6691                 premovePromoChar = promoChar;
6692                 gotPremove = 1;
6693                 if (appData.debugMode)
6694                     fprintf(debugFP, "Got premove: fromX %d,"
6695                             "fromY %d, toX %d, toY %d\n",
6696                             fromX, fromY, toX, toY);
6697             }
6698             return;
6699         }
6700         break;
6701
6702       case IcsPlayingWhite:
6703         /* User is moving for White */
6704         if (!WhiteOnMove(currentMove)) {
6705             if (!appData.premove) {
6706                 DisplayMoveError(_("It is Black's turn"));
6707             } else if (toX >= 0 && toY >= 0) {
6708                 premoveToX = toX;
6709                 premoveToY = toY;
6710                 premoveFromX = fromX;
6711                 premoveFromY = fromY;
6712                 premovePromoChar = promoChar;
6713                 gotPremove = 1;
6714                 if (appData.debugMode)
6715                     fprintf(debugFP, "Got premove: fromX %d,"
6716                             "fromY %d, toX %d, toY %d\n",
6717                             fromX, fromY, toX, toY);
6718             }
6719             return;
6720         }
6721         break;
6722
6723       default:
6724         break;
6725
6726       case EditPosition:
6727         /* EditPosition, empty square, or different color piece;
6728            click-click move is possible */
6729         if (toX == -2 || toY == -2) {
6730             boards[0][fromY][fromX] = EmptySquare;
6731             DrawPosition(FALSE, boards[currentMove]);
6732             return;
6733         } else if (toX >= 0 && toY >= 0) {
6734             boards[0][toY][toX] = boards[0][fromY][fromX];
6735             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6736                 if(boards[0][fromY][0] != EmptySquare) {
6737                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6738                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6739                 }
6740             } else
6741             if(fromX == BOARD_RGHT+1) {
6742                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6743                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6744                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6745                 }
6746             } else
6747             boards[0][fromY][fromX] = gatingPiece;
6748             DrawPosition(FALSE, boards[currentMove]);
6749             return;
6750         }
6751         return;
6752     }
6753
6754     if(toX < 0 || toY < 0) return;
6755     pup = boards[currentMove][toY][toX];
6756
6757     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6758     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6759          if( pup != EmptySquare ) return;
6760          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6761            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6762                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6763            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6764            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6765            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6766            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6767          fromY = DROP_RANK;
6768     }
6769
6770     /* [HGM] always test for legality, to get promotion info */
6771     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6772                                          fromY, fromX, toY, toX, promoChar);
6773
6774     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6775
6776     /* [HGM] but possibly ignore an IllegalMove result */
6777     if (appData.testLegality) {
6778         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6779             DisplayMoveError(_("Illegal move"));
6780             return;
6781         }
6782     }
6783
6784     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6785         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6786              ClearPremoveHighlights(); // was included
6787         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6788         return;
6789     }
6790
6791     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6792 }
6793
6794 /* Common tail of UserMoveEvent and DropMenuEvent */
6795 int
6796 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6797 {
6798     char *bookHit = 0;
6799
6800     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6801         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6802         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6803         if(WhiteOnMove(currentMove)) {
6804             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6805         } else {
6806             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6807         }
6808     }
6809
6810     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6811        move type in caller when we know the move is a legal promotion */
6812     if(moveType == NormalMove && promoChar)
6813         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6814
6815     /* [HGM] <popupFix> The following if has been moved here from
6816        UserMoveEvent(). Because it seemed to belong here (why not allow
6817        piece drops in training games?), and because it can only be
6818        performed after it is known to what we promote. */
6819     if (gameMode == Training) {
6820       /* compare the move played on the board to the next move in the
6821        * game. If they match, display the move and the opponent's response.
6822        * If they don't match, display an error message.
6823        */
6824       int saveAnimate;
6825       Board testBoard;
6826       CopyBoard(testBoard, boards[currentMove]);
6827       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6828
6829       if (CompareBoards(testBoard, boards[currentMove+1])) {
6830         ForwardInner(currentMove+1);
6831
6832         /* Autoplay the opponent's response.
6833          * if appData.animate was TRUE when Training mode was entered,
6834          * the response will be animated.
6835          */
6836         saveAnimate = appData.animate;
6837         appData.animate = animateTraining;
6838         ForwardInner(currentMove+1);
6839         appData.animate = saveAnimate;
6840
6841         /* check for the end of the game */
6842         if (currentMove >= forwardMostMove) {
6843           gameMode = PlayFromGameFile;
6844           ModeHighlight();
6845           SetTrainingModeOff();
6846           DisplayInformation(_("End of game"));
6847         }
6848       } else {
6849         DisplayError(_("Incorrect move"), 0);
6850       }
6851       return 1;
6852     }
6853
6854   /* Ok, now we know that the move is good, so we can kill
6855      the previous line in Analysis Mode */
6856   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6857                                 && currentMove < forwardMostMove) {
6858     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6859     else forwardMostMove = currentMove;
6860   }
6861
6862   ClearMap();
6863
6864   /* If we need the chess program but it's dead, restart it */
6865   ResurrectChessProgram();
6866
6867   /* A user move restarts a paused game*/
6868   if (pausing)
6869     PauseEvent();
6870
6871   thinkOutput[0] = NULLCHAR;
6872
6873   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6874
6875   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6876     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6877     return 1;
6878   }
6879
6880   if (gameMode == BeginningOfGame) {
6881     if (appData.noChessProgram) {
6882       gameMode = EditGame;
6883       SetGameInfo();
6884     } else {
6885       char buf[MSG_SIZ];
6886       gameMode = MachinePlaysBlack;
6887       StartClocks();
6888       SetGameInfo();
6889       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6890       DisplayTitle(buf);
6891       if (first.sendName) {
6892         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6893         SendToProgram(buf, &first);
6894       }
6895       StartClocks();
6896     }
6897     ModeHighlight();
6898   }
6899
6900   /* Relay move to ICS or chess engine */
6901   if (appData.icsActive) {
6902     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6903         gameMode == IcsExamining) {
6904       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6905         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6906         SendToICS("draw ");
6907         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6908       }
6909       // also send plain move, in case ICS does not understand atomic claims
6910       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6911       ics_user_moved = 1;
6912     }
6913   } else {
6914     if (first.sendTime && (gameMode == BeginningOfGame ||
6915                            gameMode == MachinePlaysWhite ||
6916                            gameMode == MachinePlaysBlack)) {
6917       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6918     }
6919     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6920          // [HGM] book: if program might be playing, let it use book
6921         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6922         first.maybeThinking = TRUE;
6923     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6924         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6925         SendBoard(&first, currentMove+1);
6926         if(second.analyzing) {
6927             if(!second.useSetboard) SendToProgram("undo\n", &second);
6928             SendBoard(&second, currentMove+1);
6929         }
6930     } else {
6931         SendMoveToProgram(forwardMostMove-1, &first);
6932         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6933     }
6934     if (currentMove == cmailOldMove + 1) {
6935       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6936     }
6937   }
6938
6939   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6940
6941   switch (gameMode) {
6942   case EditGame:
6943     if(appData.testLegality)
6944     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6945     case MT_NONE:
6946     case MT_CHECK:
6947       break;
6948     case MT_CHECKMATE:
6949     case MT_STAINMATE:
6950       if (WhiteOnMove(currentMove)) {
6951         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6952       } else {
6953         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6954       }
6955       break;
6956     case MT_STALEMATE:
6957       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6958       break;
6959     }
6960     break;
6961
6962   case MachinePlaysBlack:
6963   case MachinePlaysWhite:
6964     /* disable certain menu options while machine is thinking */
6965     SetMachineThinkingEnables();
6966     break;
6967
6968   default:
6969     break;
6970   }
6971
6972   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6973   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6974
6975   if(bookHit) { // [HGM] book: simulate book reply
6976         static char bookMove[MSG_SIZ]; // a bit generous?
6977
6978         programStats.nodes = programStats.depth = programStats.time =
6979         programStats.score = programStats.got_only_move = 0;
6980         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6981
6982         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6983         strcat(bookMove, bookHit);
6984         HandleMachineMove(bookMove, &first);
6985   }
6986   return 1;
6987 }
6988
6989 void
6990 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6991 {
6992     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6993     Markers *m = (Markers *) closure;
6994     if(rf == fromY && ff == fromX)
6995         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6996                          || kind == WhiteCapturesEnPassant
6997                          || kind == BlackCapturesEnPassant);
6998     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6999 }
7000
7001 void
7002 MarkTargetSquares (int clear)
7003 {
7004   int x, y;
7005   if(clear) // no reason to ever suppress clearing
7006     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7007   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7008      !appData.testLegality || gameMode == EditPosition) return;
7009   if(!clear) {
7010     int capt = 0;
7011     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7012     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7013       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7014       if(capt)
7015       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7016     }
7017   }
7018   DrawPosition(FALSE, NULL);
7019 }
7020
7021 int
7022 Explode (Board board, int fromX, int fromY, int toX, int toY)
7023 {
7024     if(gameInfo.variant == VariantAtomic &&
7025        (board[toY][toX] != EmptySquare ||                     // capture?
7026         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7027                          board[fromY][fromX] == BlackPawn   )
7028       )) {
7029         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7030         return TRUE;
7031     }
7032     return FALSE;
7033 }
7034
7035 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7036
7037 int
7038 CanPromote (ChessSquare piece, int y)
7039 {
7040         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7041         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7042         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7043            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7044            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7045                                                   gameInfo.variant == VariantMakruk) return FALSE;
7046         return (piece == BlackPawn && y == 1 ||
7047                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7048                 piece == BlackLance && y == 1 ||
7049                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7050 }
7051
7052 void
7053 LeftClick (ClickType clickType, int xPix, int yPix)
7054 {
7055     int x, y;
7056     Boolean saveAnimate;
7057     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7058     char promoChoice = NULLCHAR;
7059     ChessSquare piece;
7060     static TimeMark lastClickTime, prevClickTime;
7061
7062     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7063
7064     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7065
7066     if (clickType == Press) ErrorPopDown();
7067
7068     x = EventToSquare(xPix, BOARD_WIDTH);
7069     y = EventToSquare(yPix, BOARD_HEIGHT);
7070     if (!flipView && y >= 0) {
7071         y = BOARD_HEIGHT - 1 - y;
7072     }
7073     if (flipView && x >= 0) {
7074         x = BOARD_WIDTH - 1 - x;
7075     }
7076
7077     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7078         defaultPromoChoice = promoSweep;
7079         promoSweep = EmptySquare;   // terminate sweep
7080         promoDefaultAltered = TRUE;
7081         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7082     }
7083
7084     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7085         if(clickType == Release) return; // ignore upclick of click-click destination
7086         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7087         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7088         if(gameInfo.holdingsWidth &&
7089                 (WhiteOnMove(currentMove)
7090                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7091                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7092             // click in right holdings, for determining promotion piece
7093             ChessSquare p = boards[currentMove][y][x];
7094             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7095             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7096             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7097                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7098                 fromX = fromY = -1;
7099                 return;
7100             }
7101         }
7102         DrawPosition(FALSE, boards[currentMove]);
7103         return;
7104     }
7105
7106     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7107     if(clickType == Press
7108             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7109               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7110               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7111         return;
7112
7113     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7114         // could be static click on premove from-square: abort premove
7115         gotPremove = 0;
7116         ClearPremoveHighlights();
7117     }
7118
7119     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7120         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7121
7122     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7123         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7124                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7125         defaultPromoChoice = DefaultPromoChoice(side);
7126     }
7127
7128     autoQueen = appData.alwaysPromoteToQueen;
7129
7130     if (fromX == -1) {
7131       int originalY = y;
7132       gatingPiece = EmptySquare;
7133       if (clickType != Press) {
7134         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7135             DragPieceEnd(xPix, yPix); dragging = 0;
7136             DrawPosition(FALSE, NULL);
7137         }
7138         return;
7139       }
7140       doubleClick = FALSE;
7141       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7142         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7143       }
7144       fromX = x; fromY = y; toX = toY = -1;
7145       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7146          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7147          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7148             /* First square */
7149             if (OKToStartUserMove(fromX, fromY)) {
7150                 second = 0;
7151                 MarkTargetSquares(0);
7152                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7153                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7154                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7155                     promoSweep = defaultPromoChoice;
7156                     selectFlag = 0; lastX = xPix; lastY = yPix;
7157                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7158                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7159                 }
7160                 if (appData.highlightDragging) {
7161                     SetHighlights(fromX, fromY, -1, -1);
7162                 } else {
7163                     ClearHighlights();
7164                 }
7165             } else fromX = fromY = -1;
7166             return;
7167         }
7168     }
7169
7170     /* fromX != -1 */
7171     if (clickType == Press && gameMode != EditPosition) {
7172         ChessSquare fromP;
7173         ChessSquare toP;
7174         int frc;
7175
7176         // ignore off-board to clicks
7177         if(y < 0 || x < 0) return;
7178
7179         /* Check if clicking again on the same color piece */
7180         fromP = boards[currentMove][fromY][fromX];
7181         toP = boards[currentMove][y][x];
7182         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7183         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7184              WhitePawn <= toP && toP <= WhiteKing &&
7185              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7186              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7187             (BlackPawn <= fromP && fromP <= BlackKing &&
7188              BlackPawn <= toP && toP <= BlackKing &&
7189              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7190              !(fromP == BlackKing && toP == BlackRook && frc))) {
7191             /* Clicked again on same color piece -- changed his mind */
7192             second = (x == fromX && y == fromY);
7193             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7194                 second = FALSE; // first double-click rather than scond click
7195                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7196             }
7197             promoDefaultAltered = FALSE;
7198             MarkTargetSquares(1);
7199            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7200             if (appData.highlightDragging) {
7201                 SetHighlights(x, y, -1, -1);
7202             } else {
7203                 ClearHighlights();
7204             }
7205             if (OKToStartUserMove(x, y)) {
7206                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7207                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7208                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7209                  gatingPiece = boards[currentMove][fromY][fromX];
7210                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7211                 fromX = x;
7212                 fromY = y; dragging = 1;
7213                 MarkTargetSquares(0);
7214                 DragPieceBegin(xPix, yPix, FALSE);
7215                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7216                     promoSweep = defaultPromoChoice;
7217                     selectFlag = 0; lastX = xPix; lastY = yPix;
7218                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7219                 }
7220             }
7221            }
7222            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7223            second = FALSE;
7224         }
7225         // ignore clicks on holdings
7226         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7227     }
7228
7229     if (clickType == Release && x == fromX && y == fromY) {
7230         DragPieceEnd(xPix, yPix); dragging = 0;
7231         if(clearFlag) {
7232             // a deferred attempt to click-click move an empty square on top of a piece
7233             boards[currentMove][y][x] = EmptySquare;
7234             ClearHighlights();
7235             DrawPosition(FALSE, boards[currentMove]);
7236             fromX = fromY = -1; clearFlag = 0;
7237             return;
7238         }
7239         if (appData.animateDragging) {
7240             /* Undo animation damage if any */
7241             DrawPosition(FALSE, NULL);
7242         }
7243         if (second || sweepSelecting) {
7244             /* Second up/down in same square; just abort move */
7245             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7246             second = sweepSelecting = 0;
7247             fromX = fromY = -1;
7248             gatingPiece = EmptySquare;
7249             ClearHighlights();
7250             gotPremove = 0;
7251             ClearPremoveHighlights();
7252         } else {
7253             /* First upclick in same square; start click-click mode */
7254             SetHighlights(x, y, -1, -1);
7255         }
7256         return;
7257     }
7258
7259     clearFlag = 0;
7260
7261     /* we now have a different from- and (possibly off-board) to-square */
7262     /* Completed move */
7263     if(!sweepSelecting) {
7264         toX = x;
7265         toY = y;
7266     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7267
7268     saveAnimate = appData.animate;
7269     if (clickType == Press) {
7270         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7271             // must be Edit Position mode with empty-square selected
7272             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7273             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7274             return;
7275         }
7276         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7277           if(appData.sweepSelect) {
7278             ChessSquare piece = boards[currentMove][fromY][fromX];
7279             promoSweep = defaultPromoChoice;
7280             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7281             selectFlag = 0; lastX = xPix; lastY = yPix;
7282             Sweep(0); // Pawn that is going to promote: preview promotion piece
7283             sweepSelecting = 1;
7284             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7285             MarkTargetSquares(1);
7286           }
7287           return; // promo popup appears on up-click
7288         }
7289         /* Finish clickclick move */
7290         if (appData.animate || appData.highlightLastMove) {
7291             SetHighlights(fromX, fromY, toX, toY);
7292         } else {
7293             ClearHighlights();
7294         }
7295     } else {
7296 #if 0
7297 // [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
7298         /* Finish drag move */
7299         if (appData.highlightLastMove) {
7300             SetHighlights(fromX, fromY, toX, toY);
7301         } else {
7302             ClearHighlights();
7303         }
7304 #endif
7305         DragPieceEnd(xPix, yPix); dragging = 0;
7306         /* Don't animate move and drag both */
7307         appData.animate = FALSE;
7308     }
7309
7310     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7311     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7312         ChessSquare piece = boards[currentMove][fromY][fromX];
7313         if(gameMode == EditPosition && piece != EmptySquare &&
7314            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7315             int n;
7316
7317             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7318                 n = PieceToNumber(piece - (int)BlackPawn);
7319                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7320                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7321                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7322             } else
7323             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7324                 n = PieceToNumber(piece);
7325                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7326                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7327                 boards[currentMove][n][BOARD_WIDTH-2]++;
7328             }
7329             boards[currentMove][fromY][fromX] = EmptySquare;
7330         }
7331         ClearHighlights();
7332         fromX = fromY = -1;
7333         MarkTargetSquares(1);
7334         DrawPosition(TRUE, boards[currentMove]);
7335         return;
7336     }
7337
7338     // off-board moves should not be highlighted
7339     if(x < 0 || y < 0) ClearHighlights();
7340
7341     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7342
7343     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7344         SetHighlights(fromX, fromY, toX, toY);
7345         MarkTargetSquares(1);
7346         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7347             // [HGM] super: promotion to captured piece selected from holdings
7348             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7349             promotionChoice = TRUE;
7350             // kludge follows to temporarily execute move on display, without promoting yet
7351             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7352             boards[currentMove][toY][toX] = p;
7353             DrawPosition(FALSE, boards[currentMove]);
7354             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7355             boards[currentMove][toY][toX] = q;
7356             DisplayMessage("Click in holdings to choose piece", "");
7357             return;
7358         }
7359         PromotionPopUp();
7360     } else {
7361         int oldMove = currentMove;
7362         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7363         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7364         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7365         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7366            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7367             DrawPosition(TRUE, boards[currentMove]);
7368         MarkTargetSquares(1);
7369         fromX = fromY = -1;
7370     }
7371     appData.animate = saveAnimate;
7372     if (appData.animate || appData.animateDragging) {
7373         /* Undo animation damage if needed */
7374         DrawPosition(FALSE, NULL);
7375     }
7376 }
7377
7378 int
7379 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7380 {   // front-end-free part taken out of PieceMenuPopup
7381     int whichMenu; int xSqr, ySqr;
7382
7383     if(seekGraphUp) { // [HGM] seekgraph
7384         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7385         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7386         return -2;
7387     }
7388
7389     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7390          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7391         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7392         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7393         if(action == Press)   {
7394             originalFlip = flipView;
7395             flipView = !flipView; // temporarily flip board to see game from partners perspective
7396             DrawPosition(TRUE, partnerBoard);
7397             DisplayMessage(partnerStatus, "");
7398             partnerUp = TRUE;
7399         } else if(action == Release) {
7400             flipView = originalFlip;
7401             DrawPosition(TRUE, boards[currentMove]);
7402             partnerUp = FALSE;
7403         }
7404         return -2;
7405     }
7406
7407     xSqr = EventToSquare(x, BOARD_WIDTH);
7408     ySqr = EventToSquare(y, BOARD_HEIGHT);
7409     if (action == Release) {
7410         if(pieceSweep != EmptySquare) {
7411             EditPositionMenuEvent(pieceSweep, toX, toY);
7412             pieceSweep = EmptySquare;
7413         } else UnLoadPV(); // [HGM] pv
7414     }
7415     if (action != Press) return -2; // return code to be ignored
7416     switch (gameMode) {
7417       case IcsExamining:
7418         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7419       case EditPosition:
7420         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7421         if (xSqr < 0 || ySqr < 0) return -1;
7422         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7423         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7424         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7425         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7426         NextPiece(0);
7427         return 2; // grab
7428       case IcsObserving:
7429         if(!appData.icsEngineAnalyze) return -1;
7430       case IcsPlayingWhite:
7431       case IcsPlayingBlack:
7432         if(!appData.zippyPlay) goto noZip;
7433       case AnalyzeMode:
7434       case AnalyzeFile:
7435       case MachinePlaysWhite:
7436       case MachinePlaysBlack:
7437       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7438         if (!appData.dropMenu) {
7439           LoadPV(x, y);
7440           return 2; // flag front-end to grab mouse events
7441         }
7442         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7443            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7444       case EditGame:
7445       noZip:
7446         if (xSqr < 0 || ySqr < 0) return -1;
7447         if (!appData.dropMenu || appData.testLegality &&
7448             gameInfo.variant != VariantBughouse &&
7449             gameInfo.variant != VariantCrazyhouse) return -1;
7450         whichMenu = 1; // drop menu
7451         break;
7452       default:
7453         return -1;
7454     }
7455
7456     if (((*fromX = xSqr) < 0) ||
7457         ((*fromY = ySqr) < 0)) {
7458         *fromX = *fromY = -1;
7459         return -1;
7460     }
7461     if (flipView)
7462       *fromX = BOARD_WIDTH - 1 - *fromX;
7463     else
7464       *fromY = BOARD_HEIGHT - 1 - *fromY;
7465
7466     return whichMenu;
7467 }
7468
7469 void
7470 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7471 {
7472 //    char * hint = lastHint;
7473     FrontEndProgramStats stats;
7474
7475     stats.which = cps == &first ? 0 : 1;
7476     stats.depth = cpstats->depth;
7477     stats.nodes = cpstats->nodes;
7478     stats.score = cpstats->score;
7479     stats.time = cpstats->time;
7480     stats.pv = cpstats->movelist;
7481     stats.hint = lastHint;
7482     stats.an_move_index = 0;
7483     stats.an_move_count = 0;
7484
7485     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7486         stats.hint = cpstats->move_name;
7487         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7488         stats.an_move_count = cpstats->nr_moves;
7489     }
7490
7491     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
7492
7493     SetProgramStats( &stats );
7494 }
7495
7496 void
7497 ClearEngineOutputPane (int which)
7498 {
7499     static FrontEndProgramStats dummyStats;
7500     dummyStats.which = which;
7501     dummyStats.pv = "#";
7502     SetProgramStats( &dummyStats );
7503 }
7504
7505 #define MAXPLAYERS 500
7506
7507 char *
7508 TourneyStandings (int display)
7509 {
7510     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7511     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7512     char result, *p, *names[MAXPLAYERS];
7513
7514     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7515         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7516     names[0] = p = strdup(appData.participants);
7517     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7518
7519     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7520
7521     while(result = appData.results[nr]) {
7522         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7523         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7524         wScore = bScore = 0;
7525         switch(result) {
7526           case '+': wScore = 2; break;
7527           case '-': bScore = 2; break;
7528           case '=': wScore = bScore = 1; break;
7529           case ' ':
7530           case '*': return strdup("busy"); // tourney not finished
7531         }
7532         score[w] += wScore;
7533         score[b] += bScore;
7534         games[w]++;
7535         games[b]++;
7536         nr++;
7537     }
7538     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7539     for(w=0; w<nPlayers; w++) {
7540         bScore = -1;
7541         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7542         ranking[w] = b; points[w] = bScore; score[b] = -2;
7543     }
7544     p = malloc(nPlayers*34+1);
7545     for(w=0; w<nPlayers && w<display; w++)
7546         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7547     free(names[0]);
7548     return p;
7549 }
7550
7551 void
7552 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7553 {       // count all piece types
7554         int p, f, r;
7555         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7556         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7557         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7558                 p = board[r][f];
7559                 pCnt[p]++;
7560                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7561                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7562                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7563                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7564                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7565                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7566         }
7567 }
7568
7569 int
7570 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7571 {
7572         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7573         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7574
7575         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7576         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7577         if(myPawns == 2 && nMine == 3) // KPP
7578             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7579         if(myPawns == 1 && nMine == 2) // KP
7580             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7581         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7582             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7583         if(myPawns) return FALSE;
7584         if(pCnt[WhiteRook+side])
7585             return pCnt[BlackRook-side] ||
7586                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7587                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7588                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7589         if(pCnt[WhiteCannon+side]) {
7590             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7591             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7592         }
7593         if(pCnt[WhiteKnight+side])
7594             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7595         return FALSE;
7596 }
7597
7598 int
7599 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7600 {
7601         VariantClass v = gameInfo.variant;
7602
7603         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7604         if(v == VariantShatranj) return TRUE; // always winnable through baring
7605         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7606         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7607
7608         if(v == VariantXiangqi) {
7609                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7610
7611                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7612                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7613                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7614                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7615                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7616                 if(stale) // we have at least one last-rank P plus perhaps C
7617                     return majors // KPKX
7618                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7619                 else // KCA*E*
7620                     return pCnt[WhiteFerz+side] // KCAK
7621                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7622                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7623                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7624
7625         } else if(v == VariantKnightmate) {
7626                 if(nMine == 1) return FALSE;
7627                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7628         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7629                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7630
7631                 if(nMine == 1) return FALSE; // bare King
7632                 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
7633                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7634                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7635                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7636                 if(pCnt[WhiteKnight+side])
7637                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7638                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7639                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7640                 if(nBishops)
7641                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7642                 if(pCnt[WhiteAlfil+side])
7643                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7644                 if(pCnt[WhiteWazir+side])
7645                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7646         }
7647
7648         return TRUE;
7649 }
7650
7651 int
7652 CompareWithRights (Board b1, Board b2)
7653 {
7654     int rights = 0;
7655     if(!CompareBoards(b1, b2)) return FALSE;
7656     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7657     /* compare castling rights */
7658     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7659            rights++; /* King lost rights, while rook still had them */
7660     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7661         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7662            rights++; /* but at least one rook lost them */
7663     }
7664     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7665            rights++;
7666     if( b1[CASTLING][5] != NoRights ) {
7667         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7668            rights++;
7669     }
7670     return rights == 0;
7671 }
7672
7673 int
7674 Adjudicate (ChessProgramState *cps)
7675 {       // [HGM] some adjudications useful with buggy engines
7676         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7677         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7678         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7679         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7680         int k, drop, count = 0; static int bare = 1;
7681         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7682         Boolean canAdjudicate = !appData.icsActive;
7683
7684         // most tests only when we understand the game, i.e. legality-checking on
7685             if( appData.testLegality )
7686             {   /* [HGM] Some more adjudications for obstinate engines */
7687                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7688                 static int moveCount = 6;
7689                 ChessMove result;
7690                 char *reason = NULL;
7691
7692                 /* Count what is on board. */
7693                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7694
7695                 /* Some material-based adjudications that have to be made before stalemate test */
7696                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7697                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7698                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7699                      if(canAdjudicate && appData.checkMates) {
7700                          if(engineOpponent)
7701                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7702                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7703                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7704                          return 1;
7705                      }
7706                 }
7707
7708                 /* Bare King in Shatranj (loses) or Losers (wins) */
7709                 if( nrW == 1 || nrB == 1) {
7710                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7711                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7712                      if(canAdjudicate && appData.checkMates) {
7713                          if(engineOpponent)
7714                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7715                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7716                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7717                          return 1;
7718                      }
7719                   } else
7720                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7721                   {    /* bare King */
7722                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7723                         if(canAdjudicate && appData.checkMates) {
7724                             /* but only adjudicate if adjudication enabled */
7725                             if(engineOpponent)
7726                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7727                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7728                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7729                             return 1;
7730                         }
7731                   }
7732                 } else bare = 1;
7733
7734
7735             // don't wait for engine to announce game end if we can judge ourselves
7736             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7737               case MT_CHECK:
7738                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7739                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7740                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7741                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7742                             checkCnt++;
7743                         if(checkCnt >= 2) {
7744                             reason = "Xboard adjudication: 3rd check";
7745                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7746                             break;
7747                         }
7748                     }
7749                 }
7750               case MT_NONE:
7751               default:
7752                 break;
7753               case MT_STALEMATE:
7754               case MT_STAINMATE:
7755                 reason = "Xboard adjudication: Stalemate";
7756                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7757                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7758                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7759                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7760                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7761                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7762                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7763                                                                         EP_CHECKMATE : EP_WINS);
7764                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7765                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7766                 }
7767                 break;
7768               case MT_CHECKMATE:
7769                 reason = "Xboard adjudication: Checkmate";
7770                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7771                 if(gameInfo.variant == VariantShogi) {
7772                     if(forwardMostMove > backwardMostMove
7773                        && moveList[forwardMostMove-1][1] == '@'
7774                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7775                         reason = "XBoard adjudication: pawn-drop mate";
7776                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7777                     }
7778                 }
7779                 break;
7780             }
7781
7782                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7783                     case EP_STALEMATE:
7784                         result = GameIsDrawn; break;
7785                     case EP_CHECKMATE:
7786                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7787                     case EP_WINS:
7788                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7789                     default:
7790                         result = EndOfFile;
7791                 }
7792                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7793                     if(engineOpponent)
7794                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7795                     GameEnds( result, reason, GE_XBOARD );
7796                     return 1;
7797                 }
7798
7799                 /* Next absolutely insufficient mating material. */
7800                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7801                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7802                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7803
7804                      /* always flag draws, for judging claims */
7805                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7806
7807                      if(canAdjudicate && appData.materialDraws) {
7808                          /* but only adjudicate them if adjudication enabled */
7809                          if(engineOpponent) {
7810                            SendToProgram("force\n", engineOpponent); // suppress reply
7811                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7812                          }
7813                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7814                          return 1;
7815                      }
7816                 }
7817
7818                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7819                 if(gameInfo.variant == VariantXiangqi ?
7820                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7821                  : nrW + nrB == 4 &&
7822                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7823                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7824                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7825                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7826                    ) ) {
7827                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7828                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7829                           if(engineOpponent) {
7830                             SendToProgram("force\n", engineOpponent); // suppress reply
7831                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7832                           }
7833                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7834                           return 1;
7835                      }
7836                 } else moveCount = 6;
7837             }
7838
7839         // Repetition draws and 50-move rule can be applied independently of legality testing
7840
7841                 /* Check for rep-draws */
7842                 count = 0;
7843                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7844                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7845                 for(k = forwardMostMove-2;
7846                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7847                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7848                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7849                     k-=2)
7850                 {   int rights=0;
7851                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7852                         /* compare castling rights */
7853                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7854                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7855                                 rights++; /* King lost rights, while rook still had them */
7856                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7857                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7858                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7859                                    rights++; /* but at least one rook lost them */
7860                         }
7861                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7862                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7863                                 rights++;
7864                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7865                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7866                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7867                                    rights++;
7868                         }
7869                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7870                             && appData.drawRepeats > 1) {
7871                              /* adjudicate after user-specified nr of repeats */
7872                              int result = GameIsDrawn;
7873                              char *details = "XBoard adjudication: repetition draw";
7874                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7875                                 // [HGM] xiangqi: check for forbidden perpetuals
7876                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7877                                 for(m=forwardMostMove; m>k; m-=2) {
7878                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7879                                         ourPerpetual = 0; // the current mover did not always check
7880                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7881                                         hisPerpetual = 0; // the opponent did not always check
7882                                 }
7883                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7884                                                                         ourPerpetual, hisPerpetual);
7885                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7886                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7887                                     details = "Xboard adjudication: perpetual checking";
7888                                 } else
7889                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7890                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7891                                 } else
7892                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7893                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7894                                         result = BlackWins;
7895                                         details = "Xboard adjudication: repetition";
7896                                     }
7897                                 } else // it must be XQ
7898                                 // Now check for perpetual chases
7899                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7900                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7901                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7902                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7903                                         static char resdet[MSG_SIZ];
7904                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7905                                         details = resdet;
7906                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7907                                     } else
7908                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7909                                         break; // Abort repetition-checking loop.
7910                                 }
7911                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7912                              }
7913                              if(engineOpponent) {
7914                                SendToProgram("force\n", engineOpponent); // suppress reply
7915                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7916                              }
7917                              GameEnds( result, details, GE_XBOARD );
7918                              return 1;
7919                         }
7920                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7921                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7922                     }
7923                 }
7924
7925                 /* Now we test for 50-move draws. Determine ply count */
7926                 count = forwardMostMove;
7927                 /* look for last irreversble move */
7928                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7929                     count--;
7930                 /* if we hit starting position, add initial plies */
7931                 if( count == backwardMostMove )
7932                     count -= initialRulePlies;
7933                 count = forwardMostMove - count;
7934                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7935                         // adjust reversible move counter for checks in Xiangqi
7936                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7937                         if(i < backwardMostMove) i = backwardMostMove;
7938                         while(i <= forwardMostMove) {
7939                                 lastCheck = inCheck; // check evasion does not count
7940                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7941                                 if(inCheck || lastCheck) count--; // check does not count
7942                                 i++;
7943                         }
7944                 }
7945                 if( count >= 100)
7946                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7947                          /* this is used to judge if draw claims are legal */
7948                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7949                          if(engineOpponent) {
7950                            SendToProgram("force\n", engineOpponent); // suppress reply
7951                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7952                          }
7953                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7954                          return 1;
7955                 }
7956
7957                 /* if draw offer is pending, treat it as a draw claim
7958                  * when draw condition present, to allow engines a way to
7959                  * claim draws before making their move to avoid a race
7960                  * condition occurring after their move
7961                  */
7962                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7963                          char *p = NULL;
7964                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7965                              p = "Draw claim: 50-move rule";
7966                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7967                              p = "Draw claim: 3-fold repetition";
7968                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7969                              p = "Draw claim: insufficient mating material";
7970                          if( p != NULL && canAdjudicate) {
7971                              if(engineOpponent) {
7972                                SendToProgram("force\n", engineOpponent); // suppress reply
7973                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7974                              }
7975                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7976                              return 1;
7977                          }
7978                 }
7979
7980                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7981                     if(engineOpponent) {
7982                       SendToProgram("force\n", engineOpponent); // suppress reply
7983                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7984                     }
7985                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7986                     return 1;
7987                 }
7988         return 0;
7989 }
7990
7991 char *
7992 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7993 {   // [HGM] book: this routine intercepts moves to simulate book replies
7994     char *bookHit = NULL;
7995
7996     //first determine if the incoming move brings opponent into his book
7997     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7998         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7999     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8000     if(bookHit != NULL && !cps->bookSuspend) {
8001         // make sure opponent is not going to reply after receiving move to book position
8002         SendToProgram("force\n", cps);
8003         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8004     }
8005     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8006     // now arrange restart after book miss
8007     if(bookHit) {
8008         // after a book hit we never send 'go', and the code after the call to this routine
8009         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8010         char buf[MSG_SIZ], *move = bookHit;
8011         if(cps->useSAN) {
8012             int fromX, fromY, toX, toY;
8013             char promoChar;
8014             ChessMove moveType;
8015             move = buf + 30;
8016             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8017                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8018                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8019                                     PosFlags(forwardMostMove),
8020                                     fromY, fromX, toY, toX, promoChar, move);
8021             } else {
8022                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8023                 bookHit = NULL;
8024             }
8025         }
8026         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8027         SendToProgram(buf, cps);
8028         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8029     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8030         SendToProgram("go\n", cps);
8031         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8032     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8033         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8034             SendToProgram("go\n", cps);
8035         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8036     }
8037     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8038 }
8039
8040 int
8041 LoadError (char *errmess, ChessProgramState *cps)
8042 {   // unloads engine and switches back to -ncp mode if it was first
8043     if(cps->initDone) return FALSE;
8044     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8045     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8046     cps->pr = NoProc;
8047     if(cps == &first) {
8048         appData.noChessProgram = TRUE;
8049         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8050         gameMode = BeginningOfGame; ModeHighlight();
8051         SetNCPMode();
8052     }
8053     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8054     DisplayMessage("", ""); // erase waiting message
8055     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8056     return TRUE;
8057 }
8058
8059 char *savedMessage;
8060 ChessProgramState *savedState;
8061 void
8062 DeferredBookMove (void)
8063 {
8064         if(savedState->lastPing != savedState->lastPong)
8065                     ScheduleDelayedEvent(DeferredBookMove, 10);
8066         else
8067         HandleMachineMove(savedMessage, savedState);
8068 }
8069
8070 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8071 static ChessProgramState *stalledEngine;
8072 static char stashedInputMove[MSG_SIZ];
8073
8074 void
8075 HandleMachineMove (char *message, ChessProgramState *cps)
8076 {
8077     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8078     char realname[MSG_SIZ];
8079     int fromX, fromY, toX, toY;
8080     ChessMove moveType;
8081     char promoChar;
8082     char *p, *pv=buf1;
8083     int machineWhite, oldError;
8084     char *bookHit;
8085
8086     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8087         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8088         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8089             DisplayError(_("Invalid pairing from pairing engine"), 0);
8090             return;
8091         }
8092         pairingReceived = 1;
8093         NextMatchGame();
8094         return; // Skim the pairing messages here.
8095     }
8096
8097     oldError = cps->userError; cps->userError = 0;
8098
8099 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8100     /*
8101      * Kludge to ignore BEL characters
8102      */
8103     while (*message == '\007') message++;
8104
8105     /*
8106      * [HGM] engine debug message: ignore lines starting with '#' character
8107      */
8108     if(cps->debug && *message == '#') return;
8109
8110     /*
8111      * Look for book output
8112      */
8113     if (cps == &first && bookRequested) {
8114         if (message[0] == '\t' || message[0] == ' ') {
8115             /* Part of the book output is here; append it */
8116             strcat(bookOutput, message);
8117             strcat(bookOutput, "  \n");
8118             return;
8119         } else if (bookOutput[0] != NULLCHAR) {
8120             /* All of book output has arrived; display it */
8121             char *p = bookOutput;
8122             while (*p != NULLCHAR) {
8123                 if (*p == '\t') *p = ' ';
8124                 p++;
8125             }
8126             DisplayInformation(bookOutput);
8127             bookRequested = FALSE;
8128             /* Fall through to parse the current output */
8129         }
8130     }
8131
8132     /*
8133      * Look for machine move.
8134      */
8135     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8136         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8137     {
8138         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8139             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8140             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8141             stalledEngine = cps;
8142             if(appData.ponderNextMove) { // bring opponent out of ponder
8143                 if(gameMode == TwoMachinesPlay) {
8144                     if(cps->other->pause)
8145                         PauseEngine(cps->other);
8146                     else
8147                         SendToProgram("easy\n", cps->other);
8148                 }
8149             }
8150             StopClocks();
8151             return;
8152         }
8153
8154         /* This method is only useful on engines that support ping */
8155         if (cps->lastPing != cps->lastPong) {
8156           if (gameMode == BeginningOfGame) {
8157             /* Extra move from before last new; ignore */
8158             if (appData.debugMode) {
8159                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8160             }
8161           } else {
8162             if (appData.debugMode) {
8163                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8164                         cps->which, gameMode);
8165             }
8166
8167             SendToProgram("undo\n", cps);
8168           }
8169           return;
8170         }
8171
8172         switch (gameMode) {
8173           case BeginningOfGame:
8174             /* Extra move from before last reset; ignore */
8175             if (appData.debugMode) {
8176                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8177             }
8178             return;
8179
8180           case EndOfGame:
8181           case IcsIdle:
8182           default:
8183             /* Extra move after we tried to stop.  The mode test is
8184                not a reliable way of detecting this problem, but it's
8185                the best we can do on engines that don't support ping.
8186             */
8187             if (appData.debugMode) {
8188                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8189                         cps->which, gameMode);
8190             }
8191             SendToProgram("undo\n", cps);
8192             return;
8193
8194           case MachinePlaysWhite:
8195           case IcsPlayingWhite:
8196             machineWhite = TRUE;
8197             break;
8198
8199           case MachinePlaysBlack:
8200           case IcsPlayingBlack:
8201             machineWhite = FALSE;
8202             break;
8203
8204           case TwoMachinesPlay:
8205             machineWhite = (cps->twoMachinesColor[0] == 'w');
8206             break;
8207         }
8208         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8209             if (appData.debugMode) {
8210                 fprintf(debugFP,
8211                         "Ignoring move out of turn by %s, gameMode %d"
8212                         ", forwardMost %d\n",
8213                         cps->which, gameMode, forwardMostMove);
8214             }
8215             return;
8216         }
8217
8218         if(cps->alphaRank) AlphaRank(machineMove, 4);
8219         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8220                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8221             /* Machine move could not be parsed; ignore it. */
8222           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8223                     machineMove, _(cps->which));
8224             DisplayMoveError(buf1);
8225             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8226                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8227             if (gameMode == TwoMachinesPlay) {
8228               GameEnds(machineWhite ? BlackWins : WhiteWins,
8229                        buf1, GE_XBOARD);
8230             }
8231             return;
8232         }
8233
8234         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8235         /* So we have to redo legality test with true e.p. status here,  */
8236         /* to make sure an illegal e.p. capture does not slip through,   */
8237         /* to cause a forfeit on a justified illegal-move complaint      */
8238         /* of the opponent.                                              */
8239         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8240            ChessMove moveType;
8241            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8242                              fromY, fromX, toY, toX, promoChar);
8243             if(moveType == IllegalMove) {
8244               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8245                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8246                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8247                            buf1, GE_XBOARD);
8248                 return;
8249            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8250            /* [HGM] Kludge to handle engines that send FRC-style castling
8251               when they shouldn't (like TSCP-Gothic) */
8252            switch(moveType) {
8253              case WhiteASideCastleFR:
8254              case BlackASideCastleFR:
8255                toX+=2;
8256                currentMoveString[2]++;
8257                break;
8258              case WhiteHSideCastleFR:
8259              case BlackHSideCastleFR:
8260                toX--;
8261                currentMoveString[2]--;
8262                break;
8263              default: ; // nothing to do, but suppresses warning of pedantic compilers
8264            }
8265         }
8266         hintRequested = FALSE;
8267         lastHint[0] = NULLCHAR;
8268         bookRequested = FALSE;
8269         /* Program may be pondering now */
8270         cps->maybeThinking = TRUE;
8271         if (cps->sendTime == 2) cps->sendTime = 1;
8272         if (cps->offeredDraw) cps->offeredDraw--;
8273
8274         /* [AS] Save move info*/
8275         pvInfoList[ forwardMostMove ].score = programStats.score;
8276         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8277         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8278
8279         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8280
8281         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8282         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8283             int count = 0;
8284
8285             while( count < adjudicateLossPlies ) {
8286                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8287
8288                 if( count & 1 ) {
8289                     score = -score; /* Flip score for winning side */
8290                 }
8291
8292                 if( score > adjudicateLossThreshold ) {
8293                     break;
8294                 }
8295
8296                 count++;
8297             }
8298
8299             if( count >= adjudicateLossPlies ) {
8300                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8301
8302                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8303                     "Xboard adjudication",
8304                     GE_XBOARD );
8305
8306                 return;
8307             }
8308         }
8309
8310         if(Adjudicate(cps)) {
8311             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8312             return; // [HGM] adjudicate: for all automatic game ends
8313         }
8314
8315 #if ZIPPY
8316         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8317             first.initDone) {
8318           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8319                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8320                 SendToICS("draw ");
8321                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8322           }
8323           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8324           ics_user_moved = 1;
8325           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8326                 char buf[3*MSG_SIZ];
8327
8328                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8329                         programStats.score / 100.,
8330                         programStats.depth,
8331                         programStats.time / 100.,
8332                         (unsigned int)programStats.nodes,
8333                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8334                         programStats.movelist);
8335                 SendToICS(buf);
8336 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8337           }
8338         }
8339 #endif
8340
8341         /* [AS] Clear stats for next move */
8342         ClearProgramStats();
8343         thinkOutput[0] = NULLCHAR;
8344         hiddenThinkOutputState = 0;
8345
8346         bookHit = NULL;
8347         if (gameMode == TwoMachinesPlay) {
8348             /* [HGM] relaying draw offers moved to after reception of move */
8349             /* and interpreting offer as claim if it brings draw condition */
8350             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8351                 SendToProgram("draw\n", cps->other);
8352             }
8353             if (cps->other->sendTime) {
8354                 SendTimeRemaining(cps->other,
8355                                   cps->other->twoMachinesColor[0] == 'w');
8356             }
8357             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8358             if (firstMove && !bookHit) {
8359                 firstMove = FALSE;
8360                 if (cps->other->useColors) {
8361                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8362                 }
8363                 SendToProgram("go\n", cps->other);
8364             }
8365             cps->other->maybeThinking = TRUE;
8366         }
8367
8368         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8369
8370         if (!pausing && appData.ringBellAfterMoves) {
8371             RingBell();
8372         }
8373
8374         /*
8375          * Reenable menu items that were disabled while
8376          * machine was thinking
8377          */
8378         if (gameMode != TwoMachinesPlay)
8379             SetUserThinkingEnables();
8380
8381         // [HGM] book: after book hit opponent has received move and is now in force mode
8382         // force the book reply into it, and then fake that it outputted this move by jumping
8383         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8384         if(bookHit) {
8385                 static char bookMove[MSG_SIZ]; // a bit generous?
8386
8387                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8388                 strcat(bookMove, bookHit);
8389                 message = bookMove;
8390                 cps = cps->other;
8391                 programStats.nodes = programStats.depth = programStats.time =
8392                 programStats.score = programStats.got_only_move = 0;
8393                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8394
8395                 if(cps->lastPing != cps->lastPong) {
8396                     savedMessage = message; // args for deferred call
8397                     savedState = cps;
8398                     ScheduleDelayedEvent(DeferredBookMove, 10);
8399                     return;
8400                 }
8401                 goto FakeBookMove;
8402         }
8403
8404         return;
8405     }
8406
8407     /* Set special modes for chess engines.  Later something general
8408      *  could be added here; for now there is just one kludge feature,
8409      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8410      *  when "xboard" is given as an interactive command.
8411      */
8412     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8413         cps->useSigint = FALSE;
8414         cps->useSigterm = FALSE;
8415     }
8416     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8417       ParseFeatures(message+8, cps);
8418       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8419     }
8420
8421     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8422                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8423       int dummy, s=6; char buf[MSG_SIZ];
8424       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8425       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8426       if(startedFromSetupPosition) return;
8427       ParseFEN(boards[0], &dummy, message+s);
8428       DrawPosition(TRUE, boards[0]);
8429       startedFromSetupPosition = TRUE;
8430       return;
8431     }
8432     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8433      * want this, I was asked to put it in, and obliged.
8434      */
8435     if (!strncmp(message, "setboard ", 9)) {
8436         Board initial_position;
8437
8438         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8439
8440         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8441             DisplayError(_("Bad FEN received from engine"), 0);
8442             return ;
8443         } else {
8444            Reset(TRUE, FALSE);
8445            CopyBoard(boards[0], initial_position);
8446            initialRulePlies = FENrulePlies;
8447            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8448            else gameMode = MachinePlaysBlack;
8449            DrawPosition(FALSE, boards[currentMove]);
8450         }
8451         return;
8452     }
8453
8454     /*
8455      * Look for communication commands
8456      */
8457     if (!strncmp(message, "telluser ", 9)) {
8458         if(message[9] == '\\' && message[10] == '\\')
8459             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8460         PlayTellSound();
8461         DisplayNote(message + 9);
8462         return;
8463     }
8464     if (!strncmp(message, "tellusererror ", 14)) {
8465         cps->userError = 1;
8466         if(message[14] == '\\' && message[15] == '\\')
8467             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8468         PlayTellSound();
8469         DisplayError(message + 14, 0);
8470         return;
8471     }
8472     if (!strncmp(message, "tellopponent ", 13)) {
8473       if (appData.icsActive) {
8474         if (loggedOn) {
8475           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8476           SendToICS(buf1);
8477         }
8478       } else {
8479         DisplayNote(message + 13);
8480       }
8481       return;
8482     }
8483     if (!strncmp(message, "tellothers ", 11)) {
8484       if (appData.icsActive) {
8485         if (loggedOn) {
8486           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8487           SendToICS(buf1);
8488         }
8489       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8490       return;
8491     }
8492     if (!strncmp(message, "tellall ", 8)) {
8493       if (appData.icsActive) {
8494         if (loggedOn) {
8495           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8496           SendToICS(buf1);
8497         }
8498       } else {
8499         DisplayNote(message + 8);
8500       }
8501       return;
8502     }
8503     if (strncmp(message, "warning", 7) == 0) {
8504         /* Undocumented feature, use tellusererror in new code */
8505         DisplayError(message, 0);
8506         return;
8507     }
8508     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8509         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8510         strcat(realname, " query");
8511         AskQuestion(realname, buf2, buf1, cps->pr);
8512         return;
8513     }
8514     /* Commands from the engine directly to ICS.  We don't allow these to be
8515      *  sent until we are logged on. Crafty kibitzes have been known to
8516      *  interfere with the login process.
8517      */
8518     if (loggedOn) {
8519         if (!strncmp(message, "tellics ", 8)) {
8520             SendToICS(message + 8);
8521             SendToICS("\n");
8522             return;
8523         }
8524         if (!strncmp(message, "tellicsnoalias ", 15)) {
8525             SendToICS(ics_prefix);
8526             SendToICS(message + 15);
8527             SendToICS("\n");
8528             return;
8529         }
8530         /* The following are for backward compatibility only */
8531         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8532             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8533             SendToICS(ics_prefix);
8534             SendToICS(message);
8535             SendToICS("\n");
8536             return;
8537         }
8538     }
8539     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8540         return;
8541     }
8542     /*
8543      * If the move is illegal, cancel it and redraw the board.
8544      * Also deal with other error cases.  Matching is rather loose
8545      * here to accommodate engines written before the spec.
8546      */
8547     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8548         strncmp(message, "Error", 5) == 0) {
8549         if (StrStr(message, "name") ||
8550             StrStr(message, "rating") || StrStr(message, "?") ||
8551             StrStr(message, "result") || StrStr(message, "board") ||
8552             StrStr(message, "bk") || StrStr(message, "computer") ||
8553             StrStr(message, "variant") || StrStr(message, "hint") ||
8554             StrStr(message, "random") || StrStr(message, "depth") ||
8555             StrStr(message, "accepted")) {
8556             return;
8557         }
8558         if (StrStr(message, "protover")) {
8559           /* Program is responding to input, so it's apparently done
8560              initializing, and this error message indicates it is
8561              protocol version 1.  So we don't need to wait any longer
8562              for it to initialize and send feature commands. */
8563           FeatureDone(cps, 1);
8564           cps->protocolVersion = 1;
8565           return;
8566         }
8567         cps->maybeThinking = FALSE;
8568
8569         if (StrStr(message, "draw")) {
8570             /* Program doesn't have "draw" command */
8571             cps->sendDrawOffers = 0;
8572             return;
8573         }
8574         if (cps->sendTime != 1 &&
8575             (StrStr(message, "time") || StrStr(message, "otim"))) {
8576           /* Program apparently doesn't have "time" or "otim" command */
8577           cps->sendTime = 0;
8578           return;
8579         }
8580         if (StrStr(message, "analyze")) {
8581             cps->analysisSupport = FALSE;
8582             cps->analyzing = FALSE;
8583 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8584             EditGameEvent(); // [HGM] try to preserve loaded game
8585             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8586             DisplayError(buf2, 0);
8587             return;
8588         }
8589         if (StrStr(message, "(no matching move)st")) {
8590           /* Special kludge for GNU Chess 4 only */
8591           cps->stKludge = TRUE;
8592           SendTimeControl(cps, movesPerSession, timeControl,
8593                           timeIncrement, appData.searchDepth,
8594                           searchTime);
8595           return;
8596         }
8597         if (StrStr(message, "(no matching move)sd")) {
8598           /* Special kludge for GNU Chess 4 only */
8599           cps->sdKludge = TRUE;
8600           SendTimeControl(cps, movesPerSession, timeControl,
8601                           timeIncrement, appData.searchDepth,
8602                           searchTime);
8603           return;
8604         }
8605         if (!StrStr(message, "llegal")) {
8606             return;
8607         }
8608         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8609             gameMode == IcsIdle) return;
8610         if (forwardMostMove <= backwardMostMove) return;
8611         if (pausing) PauseEvent();
8612       if(appData.forceIllegal) {
8613             // [HGM] illegal: machine refused move; force position after move into it
8614           SendToProgram("force\n", cps);
8615           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8616                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8617                 // when black is to move, while there might be nothing on a2 or black
8618                 // might already have the move. So send the board as if white has the move.
8619                 // But first we must change the stm of the engine, as it refused the last move
8620                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8621                 if(WhiteOnMove(forwardMostMove)) {
8622                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8623                     SendBoard(cps, forwardMostMove); // kludgeless board
8624                 } else {
8625                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8626                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8627                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8628                 }
8629           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8630             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8631                  gameMode == TwoMachinesPlay)
8632               SendToProgram("go\n", cps);
8633             return;
8634       } else
8635         if (gameMode == PlayFromGameFile) {
8636             /* Stop reading this game file */
8637             gameMode = EditGame;
8638             ModeHighlight();
8639         }
8640         /* [HGM] illegal-move claim should forfeit game when Xboard */
8641         /* only passes fully legal moves                            */
8642         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8643             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8644                                 "False illegal-move claim", GE_XBOARD );
8645             return; // do not take back move we tested as valid
8646         }
8647         currentMove = forwardMostMove-1;
8648         DisplayMove(currentMove-1); /* before DisplayMoveError */
8649         SwitchClocks(forwardMostMove-1); // [HGM] race
8650         DisplayBothClocks();
8651         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8652                 parseList[currentMove], _(cps->which));
8653         DisplayMoveError(buf1);
8654         DrawPosition(FALSE, boards[currentMove]);
8655
8656         SetUserThinkingEnables();
8657         return;
8658     }
8659     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8660         /* Program has a broken "time" command that
8661            outputs a string not ending in newline.
8662            Don't use it. */
8663         cps->sendTime = 0;
8664     }
8665
8666     /*
8667      * If chess program startup fails, exit with an error message.
8668      * Attempts to recover here are futile. [HGM] Well, we try anyway
8669      */
8670     if ((StrStr(message, "unknown host") != NULL)
8671         || (StrStr(message, "No remote directory") != NULL)
8672         || (StrStr(message, "not found") != NULL)
8673         || (StrStr(message, "No such file") != NULL)
8674         || (StrStr(message, "can't alloc") != NULL)
8675         || (StrStr(message, "Permission denied") != NULL)) {
8676
8677         cps->maybeThinking = FALSE;
8678         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8679                 _(cps->which), cps->program, cps->host, message);
8680         RemoveInputSource(cps->isr);
8681         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8682             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8683             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8684         }
8685         return;
8686     }
8687
8688     /*
8689      * Look for hint output
8690      */
8691     if (sscanf(message, "Hint: %s", buf1) == 1) {
8692         if (cps == &first && hintRequested) {
8693             hintRequested = FALSE;
8694             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8695                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8696                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8697                                     PosFlags(forwardMostMove),
8698                                     fromY, fromX, toY, toX, promoChar, buf1);
8699                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8700                 DisplayInformation(buf2);
8701             } else {
8702                 /* Hint move could not be parsed!? */
8703               snprintf(buf2, sizeof(buf2),
8704                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8705                         buf1, _(cps->which));
8706                 DisplayError(buf2, 0);
8707             }
8708         } else {
8709           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8710         }
8711         return;
8712     }
8713
8714     /*
8715      * Ignore other messages if game is not in progress
8716      */
8717     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8718         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8719
8720     /*
8721      * look for win, lose, draw, or draw offer
8722      */
8723     if (strncmp(message, "1-0", 3) == 0) {
8724         char *p, *q, *r = "";
8725         p = strchr(message, '{');
8726         if (p) {
8727             q = strchr(p, '}');
8728             if (q) {
8729                 *q = NULLCHAR;
8730                 r = p + 1;
8731             }
8732         }
8733         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8734         return;
8735     } else if (strncmp(message, "0-1", 3) == 0) {
8736         char *p, *q, *r = "";
8737         p = strchr(message, '{');
8738         if (p) {
8739             q = strchr(p, '}');
8740             if (q) {
8741                 *q = NULLCHAR;
8742                 r = p + 1;
8743             }
8744         }
8745         /* Kludge for Arasan 4.1 bug */
8746         if (strcmp(r, "Black resigns") == 0) {
8747             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8748             return;
8749         }
8750         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8751         return;
8752     } else if (strncmp(message, "1/2", 3) == 0) {
8753         char *p, *q, *r = "";
8754         p = strchr(message, '{');
8755         if (p) {
8756             q = strchr(p, '}');
8757             if (q) {
8758                 *q = NULLCHAR;
8759                 r = p + 1;
8760             }
8761         }
8762
8763         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8764         return;
8765
8766     } else if (strncmp(message, "White resign", 12) == 0) {
8767         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8768         return;
8769     } else if (strncmp(message, "Black resign", 12) == 0) {
8770         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8771         return;
8772     } else if (strncmp(message, "White matches", 13) == 0 ||
8773                strncmp(message, "Black matches", 13) == 0   ) {
8774         /* [HGM] ignore GNUShogi noises */
8775         return;
8776     } else if (strncmp(message, "White", 5) == 0 &&
8777                message[5] != '(' &&
8778                StrStr(message, "Black") == NULL) {
8779         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8780         return;
8781     } else if (strncmp(message, "Black", 5) == 0 &&
8782                message[5] != '(') {
8783         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8784         return;
8785     } else if (strcmp(message, "resign") == 0 ||
8786                strcmp(message, "computer resigns") == 0) {
8787         switch (gameMode) {
8788           case MachinePlaysBlack:
8789           case IcsPlayingBlack:
8790             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8791             break;
8792           case MachinePlaysWhite:
8793           case IcsPlayingWhite:
8794             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8795             break;
8796           case TwoMachinesPlay:
8797             if (cps->twoMachinesColor[0] == 'w')
8798               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8799             else
8800               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8801             break;
8802           default:
8803             /* can't happen */
8804             break;
8805         }
8806         return;
8807     } else if (strncmp(message, "opponent mates", 14) == 0) {
8808         switch (gameMode) {
8809           case MachinePlaysBlack:
8810           case IcsPlayingBlack:
8811             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8812             break;
8813           case MachinePlaysWhite:
8814           case IcsPlayingWhite:
8815             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8816             break;
8817           case TwoMachinesPlay:
8818             if (cps->twoMachinesColor[0] == 'w')
8819               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8820             else
8821               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8822             break;
8823           default:
8824             /* can't happen */
8825             break;
8826         }
8827         return;
8828     } else if (strncmp(message, "computer mates", 14) == 0) {
8829         switch (gameMode) {
8830           case MachinePlaysBlack:
8831           case IcsPlayingBlack:
8832             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8833             break;
8834           case MachinePlaysWhite:
8835           case IcsPlayingWhite:
8836             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8837             break;
8838           case TwoMachinesPlay:
8839             if (cps->twoMachinesColor[0] == 'w')
8840               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8841             else
8842               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8843             break;
8844           default:
8845             /* can't happen */
8846             break;
8847         }
8848         return;
8849     } else if (strncmp(message, "checkmate", 9) == 0) {
8850         if (WhiteOnMove(forwardMostMove)) {
8851             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8852         } else {
8853             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8854         }
8855         return;
8856     } else if (strstr(message, "Draw") != NULL ||
8857                strstr(message, "game is a draw") != NULL) {
8858         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8859         return;
8860     } else if (strstr(message, "offer") != NULL &&
8861                strstr(message, "draw") != NULL) {
8862 #if ZIPPY
8863         if (appData.zippyPlay && first.initDone) {
8864             /* Relay offer to ICS */
8865             SendToICS(ics_prefix);
8866             SendToICS("draw\n");
8867         }
8868 #endif
8869         cps->offeredDraw = 2; /* valid until this engine moves twice */
8870         if (gameMode == TwoMachinesPlay) {
8871             if (cps->other->offeredDraw) {
8872                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8873             /* [HGM] in two-machine mode we delay relaying draw offer      */
8874             /* until after we also have move, to see if it is really claim */
8875             }
8876         } else if (gameMode == MachinePlaysWhite ||
8877                    gameMode == MachinePlaysBlack) {
8878           if (userOfferedDraw) {
8879             DisplayInformation(_("Machine accepts your draw offer"));
8880             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8881           } else {
8882             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8883           }
8884         }
8885     }
8886
8887
8888     /*
8889      * Look for thinking output
8890      */
8891     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8892           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8893                                 ) {
8894         int plylev, mvleft, mvtot, curscore, time;
8895         char mvname[MOVE_LEN];
8896         u64 nodes; // [DM]
8897         char plyext;
8898         int ignore = FALSE;
8899         int prefixHint = FALSE;
8900         mvname[0] = NULLCHAR;
8901
8902         switch (gameMode) {
8903           case MachinePlaysBlack:
8904           case IcsPlayingBlack:
8905             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8906             break;
8907           case MachinePlaysWhite:
8908           case IcsPlayingWhite:
8909             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8910             break;
8911           case AnalyzeMode:
8912           case AnalyzeFile:
8913             break;
8914           case IcsObserving: /* [DM] icsEngineAnalyze */
8915             if (!appData.icsEngineAnalyze) ignore = TRUE;
8916             break;
8917           case TwoMachinesPlay:
8918             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8919                 ignore = TRUE;
8920             }
8921             break;
8922           default:
8923             ignore = TRUE;
8924             break;
8925         }
8926
8927         if (!ignore) {
8928             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8929             buf1[0] = NULLCHAR;
8930             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8931                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8932
8933                 if (plyext != ' ' && plyext != '\t') {
8934                     time *= 100;
8935                 }
8936
8937                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8938                 if( cps->scoreIsAbsolute &&
8939                     ( gameMode == MachinePlaysBlack ||
8940                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8941                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8942                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8943                      !WhiteOnMove(currentMove)
8944                     ) )
8945                 {
8946                     curscore = -curscore;
8947                 }
8948
8949                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8950
8951                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8952                         char buf[MSG_SIZ];
8953                         FILE *f;
8954                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8955                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8956                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8957                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8958                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8959                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8960                                 fclose(f);
8961                         } else DisplayError(_("failed writing PV"), 0);
8962                 }
8963
8964                 tempStats.depth = plylev;
8965                 tempStats.nodes = nodes;
8966                 tempStats.time = time;
8967                 tempStats.score = curscore;
8968                 tempStats.got_only_move = 0;
8969
8970                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8971                         int ticklen;
8972
8973                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8974                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8975                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8976                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8977                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8978                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8979                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8980                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8981                 }
8982
8983                 /* Buffer overflow protection */
8984                 if (pv[0] != NULLCHAR) {
8985                     if (strlen(pv) >= sizeof(tempStats.movelist)
8986                         && appData.debugMode) {
8987                         fprintf(debugFP,
8988                                 "PV is too long; using the first %u bytes.\n",
8989                                 (unsigned) sizeof(tempStats.movelist) - 1);
8990                     }
8991
8992                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8993                 } else {
8994                     sprintf(tempStats.movelist, " no PV\n");
8995                 }
8996
8997                 if (tempStats.seen_stat) {
8998                     tempStats.ok_to_send = 1;
8999                 }
9000
9001                 if (strchr(tempStats.movelist, '(') != NULL) {
9002                     tempStats.line_is_book = 1;
9003                     tempStats.nr_moves = 0;
9004                     tempStats.moves_left = 0;
9005                 } else {
9006                     tempStats.line_is_book = 0;
9007                 }
9008
9009                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9010                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9011
9012                 SendProgramStatsToFrontend( cps, &tempStats );
9013
9014                 /*
9015                     [AS] Protect the thinkOutput buffer from overflow... this
9016                     is only useful if buf1 hasn't overflowed first!
9017                 */
9018                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9019                          plylev,
9020                          (gameMode == TwoMachinesPlay ?
9021                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9022                          ((double) curscore) / 100.0,
9023                          prefixHint ? lastHint : "",
9024                          prefixHint ? " " : "" );
9025
9026                 if( buf1[0] != NULLCHAR ) {
9027                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9028
9029                     if( strlen(pv) > max_len ) {
9030                         if( appData.debugMode) {
9031                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9032                         }
9033                         pv[max_len+1] = '\0';
9034                     }
9035
9036                     strcat( thinkOutput, pv);
9037                 }
9038
9039                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9040                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9041                     DisplayMove(currentMove - 1);
9042                 }
9043                 return;
9044
9045             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9046                 /* crafty (9.25+) says "(only move) <move>"
9047                  * if there is only 1 legal move
9048                  */
9049                 sscanf(p, "(only move) %s", buf1);
9050                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9051                 sprintf(programStats.movelist, "%s (only move)", buf1);
9052                 programStats.depth = 1;
9053                 programStats.nr_moves = 1;
9054                 programStats.moves_left = 1;
9055                 programStats.nodes = 1;
9056                 programStats.time = 1;
9057                 programStats.got_only_move = 1;
9058
9059                 /* Not really, but we also use this member to
9060                    mean "line isn't going to change" (Crafty
9061                    isn't searching, so stats won't change) */
9062                 programStats.line_is_book = 1;
9063
9064                 SendProgramStatsToFrontend( cps, &programStats );
9065
9066                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9067                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9068                     DisplayMove(currentMove - 1);
9069                 }
9070                 return;
9071             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9072                               &time, &nodes, &plylev, &mvleft,
9073                               &mvtot, mvname) >= 5) {
9074                 /* The stat01: line is from Crafty (9.29+) in response
9075                    to the "." command */
9076                 programStats.seen_stat = 1;
9077                 cps->maybeThinking = TRUE;
9078
9079                 if (programStats.got_only_move || !appData.periodicUpdates)
9080                   return;
9081
9082                 programStats.depth = plylev;
9083                 programStats.time = time;
9084                 programStats.nodes = nodes;
9085                 programStats.moves_left = mvleft;
9086                 programStats.nr_moves = mvtot;
9087                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9088                 programStats.ok_to_send = 1;
9089                 programStats.movelist[0] = '\0';
9090
9091                 SendProgramStatsToFrontend( cps, &programStats );
9092
9093                 return;
9094
9095             } else if (strncmp(message,"++",2) == 0) {
9096                 /* Crafty 9.29+ outputs this */
9097                 programStats.got_fail = 2;
9098                 return;
9099
9100             } else if (strncmp(message,"--",2) == 0) {
9101                 /* Crafty 9.29+ outputs this */
9102                 programStats.got_fail = 1;
9103                 return;
9104
9105             } else if (thinkOutput[0] != NULLCHAR &&
9106                        strncmp(message, "    ", 4) == 0) {
9107                 unsigned message_len;
9108
9109                 p = message;
9110                 while (*p && *p == ' ') p++;
9111
9112                 message_len = strlen( p );
9113
9114                 /* [AS] Avoid buffer overflow */
9115                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9116                     strcat(thinkOutput, " ");
9117                     strcat(thinkOutput, p);
9118                 }
9119
9120                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9121                     strcat(programStats.movelist, " ");
9122                     strcat(programStats.movelist, p);
9123                 }
9124
9125                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9126                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9127                     DisplayMove(currentMove - 1);
9128                 }
9129                 return;
9130             }
9131         }
9132         else {
9133             buf1[0] = NULLCHAR;
9134
9135             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9136                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9137             {
9138                 ChessProgramStats cpstats;
9139
9140                 if (plyext != ' ' && plyext != '\t') {
9141                     time *= 100;
9142                 }
9143
9144                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9145                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9146                     curscore = -curscore;
9147                 }
9148
9149                 cpstats.depth = plylev;
9150                 cpstats.nodes = nodes;
9151                 cpstats.time = time;
9152                 cpstats.score = curscore;
9153                 cpstats.got_only_move = 0;
9154                 cpstats.movelist[0] = '\0';
9155
9156                 if (buf1[0] != NULLCHAR) {
9157                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9158                 }
9159
9160                 cpstats.ok_to_send = 0;
9161                 cpstats.line_is_book = 0;
9162                 cpstats.nr_moves = 0;
9163                 cpstats.moves_left = 0;
9164
9165                 SendProgramStatsToFrontend( cps, &cpstats );
9166             }
9167         }
9168     }
9169 }
9170
9171
9172 /* Parse a game score from the character string "game", and
9173    record it as the history of the current game.  The game
9174    score is NOT assumed to start from the standard position.
9175    The display is not updated in any way.
9176    */
9177 void
9178 ParseGameHistory (char *game)
9179 {
9180     ChessMove moveType;
9181     int fromX, fromY, toX, toY, boardIndex;
9182     char promoChar;
9183     char *p, *q;
9184     char buf[MSG_SIZ];
9185
9186     if (appData.debugMode)
9187       fprintf(debugFP, "Parsing game history: %s\n", game);
9188
9189     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9190     gameInfo.site = StrSave(appData.icsHost);
9191     gameInfo.date = PGNDate();
9192     gameInfo.round = StrSave("-");
9193
9194     /* Parse out names of players */
9195     while (*game == ' ') game++;
9196     p = buf;
9197     while (*game != ' ') *p++ = *game++;
9198     *p = NULLCHAR;
9199     gameInfo.white = StrSave(buf);
9200     while (*game == ' ') game++;
9201     p = buf;
9202     while (*game != ' ' && *game != '\n') *p++ = *game++;
9203     *p = NULLCHAR;
9204     gameInfo.black = StrSave(buf);
9205
9206     /* Parse moves */
9207     boardIndex = blackPlaysFirst ? 1 : 0;
9208     yynewstr(game);
9209     for (;;) {
9210         yyboardindex = boardIndex;
9211         moveType = (ChessMove) Myylex();
9212         switch (moveType) {
9213           case IllegalMove:             /* maybe suicide chess, etc. */
9214   if (appData.debugMode) {
9215     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9216     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9217     setbuf(debugFP, NULL);
9218   }
9219           case WhitePromotion:
9220           case BlackPromotion:
9221           case WhiteNonPromotion:
9222           case BlackNonPromotion:
9223           case NormalMove:
9224           case WhiteCapturesEnPassant:
9225           case BlackCapturesEnPassant:
9226           case WhiteKingSideCastle:
9227           case WhiteQueenSideCastle:
9228           case BlackKingSideCastle:
9229           case BlackQueenSideCastle:
9230           case WhiteKingSideCastleWild:
9231           case WhiteQueenSideCastleWild:
9232           case BlackKingSideCastleWild:
9233           case BlackQueenSideCastleWild:
9234           /* PUSH Fabien */
9235           case WhiteHSideCastleFR:
9236           case WhiteASideCastleFR:
9237           case BlackHSideCastleFR:
9238           case BlackASideCastleFR:
9239           /* POP Fabien */
9240             fromX = currentMoveString[0] - AAA;
9241             fromY = currentMoveString[1] - ONE;
9242             toX = currentMoveString[2] - AAA;
9243             toY = currentMoveString[3] - ONE;
9244             promoChar = currentMoveString[4];
9245             break;
9246           case WhiteDrop:
9247           case BlackDrop:
9248             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9249             fromX = moveType == WhiteDrop ?
9250               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9251             (int) CharToPiece(ToLower(currentMoveString[0]));
9252             fromY = DROP_RANK;
9253             toX = currentMoveString[2] - AAA;
9254             toY = currentMoveString[3] - ONE;
9255             promoChar = NULLCHAR;
9256             break;
9257           case AmbiguousMove:
9258             /* bug? */
9259             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9260   if (appData.debugMode) {
9261     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9262     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9263     setbuf(debugFP, NULL);
9264   }
9265             DisplayError(buf, 0);
9266             return;
9267           case ImpossibleMove:
9268             /* bug? */
9269             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9270   if (appData.debugMode) {
9271     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9272     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9273     setbuf(debugFP, NULL);
9274   }
9275             DisplayError(buf, 0);
9276             return;
9277           case EndOfFile:
9278             if (boardIndex < backwardMostMove) {
9279                 /* Oops, gap.  How did that happen? */
9280                 DisplayError(_("Gap in move list"), 0);
9281                 return;
9282             }
9283             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9284             if (boardIndex > forwardMostMove) {
9285                 forwardMostMove = boardIndex;
9286             }
9287             return;
9288           case ElapsedTime:
9289             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9290                 strcat(parseList[boardIndex-1], " ");
9291                 strcat(parseList[boardIndex-1], yy_text);
9292             }
9293             continue;
9294           case Comment:
9295           case PGNTag:
9296           case NAG:
9297           default:
9298             /* ignore */
9299             continue;
9300           case WhiteWins:
9301           case BlackWins:
9302           case GameIsDrawn:
9303           case GameUnfinished:
9304             if (gameMode == IcsExamining) {
9305                 if (boardIndex < backwardMostMove) {
9306                     /* Oops, gap.  How did that happen? */
9307                     return;
9308                 }
9309                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9310                 return;
9311             }
9312             gameInfo.result = moveType;
9313             p = strchr(yy_text, '{');
9314             if (p == NULL) p = strchr(yy_text, '(');
9315             if (p == NULL) {
9316                 p = yy_text;
9317                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9318             } else {
9319                 q = strchr(p, *p == '{' ? '}' : ')');
9320                 if (q != NULL) *q = NULLCHAR;
9321                 p++;
9322             }
9323             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9324             gameInfo.resultDetails = StrSave(p);
9325             continue;
9326         }
9327         if (boardIndex >= forwardMostMove &&
9328             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9329             backwardMostMove = blackPlaysFirst ? 1 : 0;
9330             return;
9331         }
9332         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9333                                  fromY, fromX, toY, toX, promoChar,
9334                                  parseList[boardIndex]);
9335         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9336         /* currentMoveString is set as a side-effect of yylex */
9337         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9338         strcat(moveList[boardIndex], "\n");
9339         boardIndex++;
9340         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9341         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9342           case MT_NONE:
9343           case MT_STALEMATE:
9344           default:
9345             break;
9346           case MT_CHECK:
9347             if(gameInfo.variant != VariantShogi)
9348                 strcat(parseList[boardIndex - 1], "+");
9349             break;
9350           case MT_CHECKMATE:
9351           case MT_STAINMATE:
9352             strcat(parseList[boardIndex - 1], "#");
9353             break;
9354         }
9355     }
9356 }
9357
9358
9359 /* Apply a move to the given board  */
9360 void
9361 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9362 {
9363   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9364   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9365
9366     /* [HGM] compute & store e.p. status and castling rights for new position */
9367     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9368
9369       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9370       oldEP = (signed char)board[EP_STATUS];
9371       board[EP_STATUS] = EP_NONE;
9372
9373   if (fromY == DROP_RANK) {
9374         /* must be first */
9375         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9376             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9377             return;
9378         }
9379         piece = board[toY][toX] = (ChessSquare) fromX;
9380   } else {
9381       int i;
9382
9383       if( board[toY][toX] != EmptySquare )
9384            board[EP_STATUS] = EP_CAPTURE;
9385
9386       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9387            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9388                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9389       } else
9390       if( board[fromY][fromX] == WhitePawn ) {
9391            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9392                board[EP_STATUS] = EP_PAWN_MOVE;
9393            if( toY-fromY==2) {
9394                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9395                         gameInfo.variant != VariantBerolina || toX < fromX)
9396                       board[EP_STATUS] = toX | berolina;
9397                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9398                         gameInfo.variant != VariantBerolina || toX > fromX)
9399                       board[EP_STATUS] = toX;
9400            }
9401       } else
9402       if( board[fromY][fromX] == BlackPawn ) {
9403            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9404                board[EP_STATUS] = EP_PAWN_MOVE;
9405            if( toY-fromY== -2) {
9406                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9407                         gameInfo.variant != VariantBerolina || toX < fromX)
9408                       board[EP_STATUS] = toX | berolina;
9409                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9410                         gameInfo.variant != VariantBerolina || toX > fromX)
9411                       board[EP_STATUS] = toX;
9412            }
9413        }
9414
9415        for(i=0; i<nrCastlingRights; i++) {
9416            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9417               board[CASTLING][i] == toX   && castlingRank[i] == toY
9418              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9419        }
9420
9421        if(gameInfo.variant == VariantSChess) { // update virginity
9422            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9423            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9424            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9425            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9426        }
9427
9428      if (fromX == toX && fromY == toY) return;
9429
9430      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9431      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9432      if(gameInfo.variant == VariantKnightmate)
9433          king += (int) WhiteUnicorn - (int) WhiteKing;
9434
9435     /* Code added by Tord: */
9436     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9437     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9438         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9439       board[fromY][fromX] = EmptySquare;
9440       board[toY][toX] = EmptySquare;
9441       if((toX > fromX) != (piece == WhiteRook)) {
9442         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9443       } else {
9444         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9445       }
9446     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9447                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9448       board[fromY][fromX] = EmptySquare;
9449       board[toY][toX] = EmptySquare;
9450       if((toX > fromX) != (piece == BlackRook)) {
9451         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9452       } else {
9453         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9454       }
9455     /* End of code added by Tord */
9456
9457     } else if (board[fromY][fromX] == king
9458         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9459         && toY == fromY && toX > fromX+1) {
9460         board[fromY][fromX] = EmptySquare;
9461         board[toY][toX] = king;
9462         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9463         board[fromY][BOARD_RGHT-1] = EmptySquare;
9464     } else if (board[fromY][fromX] == king
9465         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9466                && toY == fromY && toX < fromX-1) {
9467         board[fromY][fromX] = EmptySquare;
9468         board[toY][toX] = king;
9469         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9470         board[fromY][BOARD_LEFT] = EmptySquare;
9471     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9472                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9473                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9474                ) {
9475         /* white pawn promotion */
9476         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9477         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9478             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9479         board[fromY][fromX] = EmptySquare;
9480     } else if ((fromY >= BOARD_HEIGHT>>1)
9481                && (toX != fromX)
9482                && gameInfo.variant != VariantXiangqi
9483                && gameInfo.variant != VariantBerolina
9484                && (board[fromY][fromX] == WhitePawn)
9485                && (board[toY][toX] == EmptySquare)) {
9486         board[fromY][fromX] = EmptySquare;
9487         board[toY][toX] = WhitePawn;
9488         captured = board[toY - 1][toX];
9489         board[toY - 1][toX] = EmptySquare;
9490     } else if ((fromY == BOARD_HEIGHT-4)
9491                && (toX == fromX)
9492                && gameInfo.variant == VariantBerolina
9493                && (board[fromY][fromX] == WhitePawn)
9494                && (board[toY][toX] == EmptySquare)) {
9495         board[fromY][fromX] = EmptySquare;
9496         board[toY][toX] = WhitePawn;
9497         if(oldEP & EP_BEROLIN_A) {
9498                 captured = board[fromY][fromX-1];
9499                 board[fromY][fromX-1] = EmptySquare;
9500         }else{  captured = board[fromY][fromX+1];
9501                 board[fromY][fromX+1] = EmptySquare;
9502         }
9503     } else if (board[fromY][fromX] == king
9504         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9505                && toY == fromY && toX > fromX+1) {
9506         board[fromY][fromX] = EmptySquare;
9507         board[toY][toX] = king;
9508         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9509         board[fromY][BOARD_RGHT-1] = EmptySquare;
9510     } else if (board[fromY][fromX] == king
9511         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9512                && toY == fromY && toX < fromX-1) {
9513         board[fromY][fromX] = EmptySquare;
9514         board[toY][toX] = king;
9515         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9516         board[fromY][BOARD_LEFT] = EmptySquare;
9517     } else if (fromY == 7 && fromX == 3
9518                && board[fromY][fromX] == BlackKing
9519                && toY == 7 && toX == 5) {
9520         board[fromY][fromX] = EmptySquare;
9521         board[toY][toX] = BlackKing;
9522         board[fromY][7] = EmptySquare;
9523         board[toY][4] = BlackRook;
9524     } else if (fromY == 7 && fromX == 3
9525                && board[fromY][fromX] == BlackKing
9526                && toY == 7 && toX == 1) {
9527         board[fromY][fromX] = EmptySquare;
9528         board[toY][toX] = BlackKing;
9529         board[fromY][0] = EmptySquare;
9530         board[toY][2] = BlackRook;
9531     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9532                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9533                && toY < promoRank && promoChar
9534                ) {
9535         /* black pawn promotion */
9536         board[toY][toX] = CharToPiece(ToLower(promoChar));
9537         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9538             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9539         board[fromY][fromX] = EmptySquare;
9540     } else if ((fromY < BOARD_HEIGHT>>1)
9541                && (toX != fromX)
9542                && gameInfo.variant != VariantXiangqi
9543                && gameInfo.variant != VariantBerolina
9544                && (board[fromY][fromX] == BlackPawn)
9545                && (board[toY][toX] == EmptySquare)) {
9546         board[fromY][fromX] = EmptySquare;
9547         board[toY][toX] = BlackPawn;
9548         captured = board[toY + 1][toX];
9549         board[toY + 1][toX] = EmptySquare;
9550     } else if ((fromY == 3)
9551                && (toX == fromX)
9552                && gameInfo.variant == VariantBerolina
9553                && (board[fromY][fromX] == BlackPawn)
9554                && (board[toY][toX] == EmptySquare)) {
9555         board[fromY][fromX] = EmptySquare;
9556         board[toY][toX] = BlackPawn;
9557         if(oldEP & EP_BEROLIN_A) {
9558                 captured = board[fromY][fromX-1];
9559                 board[fromY][fromX-1] = EmptySquare;
9560         }else{  captured = board[fromY][fromX+1];
9561                 board[fromY][fromX+1] = EmptySquare;
9562         }
9563     } else {
9564         board[toY][toX] = board[fromY][fromX];
9565         board[fromY][fromX] = EmptySquare;
9566     }
9567   }
9568
9569     if (gameInfo.holdingsWidth != 0) {
9570
9571       /* !!A lot more code needs to be written to support holdings  */
9572       /* [HGM] OK, so I have written it. Holdings are stored in the */
9573       /* penultimate board files, so they are automaticlly stored   */
9574       /* in the game history.                                       */
9575       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9576                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9577         /* Delete from holdings, by decreasing count */
9578         /* and erasing image if necessary            */
9579         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9580         if(p < (int) BlackPawn) { /* white drop */
9581              p -= (int)WhitePawn;
9582                  p = PieceToNumber((ChessSquare)p);
9583              if(p >= gameInfo.holdingsSize) p = 0;
9584              if(--board[p][BOARD_WIDTH-2] <= 0)
9585                   board[p][BOARD_WIDTH-1] = EmptySquare;
9586              if((int)board[p][BOARD_WIDTH-2] < 0)
9587                         board[p][BOARD_WIDTH-2] = 0;
9588         } else {                  /* black drop */
9589              p -= (int)BlackPawn;
9590                  p = PieceToNumber((ChessSquare)p);
9591              if(p >= gameInfo.holdingsSize) p = 0;
9592              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9593                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9594              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9595                         board[BOARD_HEIGHT-1-p][1] = 0;
9596         }
9597       }
9598       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9599           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9600         /* [HGM] holdings: Add to holdings, if holdings exist */
9601         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9602                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9603                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9604         }
9605         p = (int) captured;
9606         if (p >= (int) BlackPawn) {
9607           p -= (int)BlackPawn;
9608           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9609                   /* in Shogi restore piece to its original  first */
9610                   captured = (ChessSquare) (DEMOTED captured);
9611                   p = DEMOTED p;
9612           }
9613           p = PieceToNumber((ChessSquare)p);
9614           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9615           board[p][BOARD_WIDTH-2]++;
9616           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9617         } else {
9618           p -= (int)WhitePawn;
9619           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9620                   captured = (ChessSquare) (DEMOTED captured);
9621                   p = DEMOTED p;
9622           }
9623           p = PieceToNumber((ChessSquare)p);
9624           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9625           board[BOARD_HEIGHT-1-p][1]++;
9626           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9627         }
9628       }
9629     } else if (gameInfo.variant == VariantAtomic) {
9630       if (captured != EmptySquare) {
9631         int y, x;
9632         for (y = toY-1; y <= toY+1; y++) {
9633           for (x = toX-1; x <= toX+1; x++) {
9634             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9635                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9636               board[y][x] = EmptySquare;
9637             }
9638           }
9639         }
9640         board[toY][toX] = EmptySquare;
9641       }
9642     }
9643     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9644         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9645     } else
9646     if(promoChar == '+') {
9647         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9648         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9649     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9650         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9651         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9652            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9653         board[toY][toX] = newPiece;
9654     }
9655     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9656                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9657         // [HGM] superchess: take promotion piece out of holdings
9658         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9659         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9660             if(!--board[k][BOARD_WIDTH-2])
9661                 board[k][BOARD_WIDTH-1] = EmptySquare;
9662         } else {
9663             if(!--board[BOARD_HEIGHT-1-k][1])
9664                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9665         }
9666     }
9667
9668 }
9669
9670 /* Updates forwardMostMove */
9671 void
9672 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9673 {
9674 //    forwardMostMove++; // [HGM] bare: moved downstream
9675
9676     (void) CoordsToAlgebraic(boards[forwardMostMove],
9677                              PosFlags(forwardMostMove),
9678                              fromY, fromX, toY, toX, promoChar,
9679                              parseList[forwardMostMove]);
9680
9681     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9682         int timeLeft; static int lastLoadFlag=0; int king, piece;
9683         piece = boards[forwardMostMove][fromY][fromX];
9684         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9685         if(gameInfo.variant == VariantKnightmate)
9686             king += (int) WhiteUnicorn - (int) WhiteKing;
9687         if(forwardMostMove == 0) {
9688             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9689                 fprintf(serverMoves, "%s;", UserName());
9690             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9691                 fprintf(serverMoves, "%s;", second.tidy);
9692             fprintf(serverMoves, "%s;", first.tidy);
9693             if(gameMode == MachinePlaysWhite)
9694                 fprintf(serverMoves, "%s;", UserName());
9695             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9696                 fprintf(serverMoves, "%s;", second.tidy);
9697         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9698         lastLoadFlag = loadFlag;
9699         // print base move
9700         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9701         // print castling suffix
9702         if( toY == fromY && piece == king ) {
9703             if(toX-fromX > 1)
9704                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9705             if(fromX-toX >1)
9706                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9707         }
9708         // e.p. suffix
9709         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9710              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9711              boards[forwardMostMove][toY][toX] == EmptySquare
9712              && fromX != toX && fromY != toY)
9713                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9714         // promotion suffix
9715         if(promoChar != NULLCHAR) {
9716             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9717                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9718                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9719             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9720         }
9721         if(!loadFlag) {
9722                 char buf[MOVE_LEN*2], *p; int len;
9723             fprintf(serverMoves, "/%d/%d",
9724                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9725             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9726             else                      timeLeft = blackTimeRemaining/1000;
9727             fprintf(serverMoves, "/%d", timeLeft);
9728                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9729                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9730                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9731                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9732             fprintf(serverMoves, "/%s", buf);
9733         }
9734         fflush(serverMoves);
9735     }
9736
9737     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9738         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9739       return;
9740     }
9741     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9742     if (commentList[forwardMostMove+1] != NULL) {
9743         free(commentList[forwardMostMove+1]);
9744         commentList[forwardMostMove+1] = NULL;
9745     }
9746     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9747     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9748     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9749     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9750     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9751     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9752     adjustedClock = FALSE;
9753     gameInfo.result = GameUnfinished;
9754     if (gameInfo.resultDetails != NULL) {
9755         free(gameInfo.resultDetails);
9756         gameInfo.resultDetails = NULL;
9757     }
9758     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9759                               moveList[forwardMostMove - 1]);
9760     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9761       case MT_NONE:
9762       case MT_STALEMATE:
9763       default:
9764         break;
9765       case MT_CHECK:
9766         if(gameInfo.variant != VariantShogi)
9767             strcat(parseList[forwardMostMove - 1], "+");
9768         break;
9769       case MT_CHECKMATE:
9770       case MT_STAINMATE:
9771         strcat(parseList[forwardMostMove - 1], "#");
9772         break;
9773     }
9774
9775 }
9776
9777 /* Updates currentMove if not pausing */
9778 void
9779 ShowMove (int fromX, int fromY, int toX, int toY)
9780 {
9781     int instant = (gameMode == PlayFromGameFile) ?
9782         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9783     if(appData.noGUI) return;
9784     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9785         if (!instant) {
9786             if (forwardMostMove == currentMove + 1) {
9787                 AnimateMove(boards[forwardMostMove - 1],
9788                             fromX, fromY, toX, toY);
9789             }
9790         }
9791         currentMove = forwardMostMove;
9792     }
9793
9794     if (instant) return;
9795
9796     DisplayMove(currentMove - 1);
9797     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9798             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9799                 SetHighlights(fromX, fromY, toX, toY);
9800             }
9801     }
9802     DrawPosition(FALSE, boards[currentMove]);
9803     DisplayBothClocks();
9804     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9805 }
9806
9807 void
9808 SendEgtPath (ChessProgramState *cps)
9809 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9810         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9811
9812         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9813
9814         while(*p) {
9815             char c, *q = name+1, *r, *s;
9816
9817             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9818             while(*p && *p != ',') *q++ = *p++;
9819             *q++ = ':'; *q = 0;
9820             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9821                 strcmp(name, ",nalimov:") == 0 ) {
9822                 // take nalimov path from the menu-changeable option first, if it is defined
9823               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9824                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9825             } else
9826             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9827                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9828                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9829                 s = r = StrStr(s, ":") + 1; // beginning of path info
9830                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9831                 c = *r; *r = 0;             // temporarily null-terminate path info
9832                     *--q = 0;               // strip of trailig ':' from name
9833                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9834                 *r = c;
9835                 SendToProgram(buf,cps);     // send egtbpath command for this format
9836             }
9837             if(*p == ',') p++; // read away comma to position for next format name
9838         }
9839 }
9840
9841 void
9842 InitChessProgram (ChessProgramState *cps, int setup)
9843 /* setup needed to setup FRC opening position */
9844 {
9845     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9846     if (appData.noChessProgram) return;
9847     hintRequested = FALSE;
9848     bookRequested = FALSE;
9849
9850     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9851     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9852     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9853     if(cps->memSize) { /* [HGM] memory */
9854       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9855         SendToProgram(buf, cps);
9856     }
9857     SendEgtPath(cps); /* [HGM] EGT */
9858     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9859       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9860         SendToProgram(buf, cps);
9861     }
9862
9863     SendToProgram(cps->initString, cps);
9864     if (gameInfo.variant != VariantNormal &&
9865         gameInfo.variant != VariantLoadable
9866         /* [HGM] also send variant if board size non-standard */
9867         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9868                                             ) {
9869       char *v = VariantName(gameInfo.variant);
9870       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9871         /* [HGM] in protocol 1 we have to assume all variants valid */
9872         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9873         DisplayFatalError(buf, 0, 1);
9874         return;
9875       }
9876
9877       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9878       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9879       if( gameInfo.variant == VariantXiangqi )
9880            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9881       if( gameInfo.variant == VariantShogi )
9882            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9883       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9884            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9885       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9886           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9887            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9888       if( gameInfo.variant == VariantCourier )
9889            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9890       if( gameInfo.variant == VariantSuper )
9891            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9892       if( gameInfo.variant == VariantGreat )
9893            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9894       if( gameInfo.variant == VariantSChess )
9895            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9896       if( gameInfo.variant == VariantGrand )
9897            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9898
9899       if(overruled) {
9900         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9901                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9902            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9903            if(StrStr(cps->variants, b) == NULL) {
9904                // specific sized variant not known, check if general sizing allowed
9905                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9906                    if(StrStr(cps->variants, "boardsize") == NULL) {
9907                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9908                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9909                        DisplayFatalError(buf, 0, 1);
9910                        return;
9911                    }
9912                    /* [HGM] here we really should compare with the maximum supported board size */
9913                }
9914            }
9915       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9916       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9917       SendToProgram(buf, cps);
9918     }
9919     currentlyInitializedVariant = gameInfo.variant;
9920
9921     /* [HGM] send opening position in FRC to first engine */
9922     if(setup) {
9923           SendToProgram("force\n", cps);
9924           SendBoard(cps, 0);
9925           /* engine is now in force mode! Set flag to wake it up after first move. */
9926           setboardSpoiledMachineBlack = 1;
9927     }
9928
9929     if (cps->sendICS) {
9930       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9931       SendToProgram(buf, cps);
9932     }
9933     cps->maybeThinking = FALSE;
9934     cps->offeredDraw = 0;
9935     if (!appData.icsActive) {
9936         SendTimeControl(cps, movesPerSession, timeControl,
9937                         timeIncrement, appData.searchDepth,
9938                         searchTime);
9939     }
9940     if (appData.showThinking
9941         // [HGM] thinking: four options require thinking output to be sent
9942         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9943                                 ) {
9944         SendToProgram("post\n", cps);
9945     }
9946     SendToProgram("hard\n", cps);
9947     if (!appData.ponderNextMove) {
9948         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9949            it without being sure what state we are in first.  "hard"
9950            is not a toggle, so that one is OK.
9951          */
9952         SendToProgram("easy\n", cps);
9953     }
9954     if (cps->usePing) {
9955       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9956       SendToProgram(buf, cps);
9957     }
9958     cps->initDone = TRUE;
9959     ClearEngineOutputPane(cps == &second);
9960 }
9961
9962
9963 void
9964 ResendOptions (ChessProgramState *cps)
9965 { // send the stored value of the options
9966   int i;
9967   char buf[MSG_SIZ];
9968   Option *opt = cps->option;
9969   for(i=0; i<cps->nrOptions; i++, opt++) {
9970       switch(opt->type) {
9971         case Spin:
9972         case Slider:
9973         case CheckBox:
9974             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9975           break;
9976         case ComboBox:
9977           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9978           break;
9979         default:
9980             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9981           break;
9982         case Button:
9983         case SaveButton:
9984           continue;
9985       }
9986       SendToProgram(buf, cps);
9987   }
9988 }
9989
9990 void
9991 StartChessProgram (ChessProgramState *cps)
9992 {
9993     char buf[MSG_SIZ];
9994     int err;
9995
9996     if (appData.noChessProgram) return;
9997     cps->initDone = FALSE;
9998
9999     if (strcmp(cps->host, "localhost") == 0) {
10000         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10001     } else if (*appData.remoteShell == NULLCHAR) {
10002         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10003     } else {
10004         if (*appData.remoteUser == NULLCHAR) {
10005           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10006                     cps->program);
10007         } else {
10008           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10009                     cps->host, appData.remoteUser, cps->program);
10010         }
10011         err = StartChildProcess(buf, "", &cps->pr);
10012     }
10013
10014     if (err != 0) {
10015       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10016         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10017         if(cps != &first) return;
10018         appData.noChessProgram = TRUE;
10019         ThawUI();
10020         SetNCPMode();
10021 //      DisplayFatalError(buf, err, 1);
10022 //      cps->pr = NoProc;
10023 //      cps->isr = NULL;
10024         return;
10025     }
10026
10027     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10028     if (cps->protocolVersion > 1) {
10029       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10030       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10031         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10032         cps->comboCnt = 0;  //                and values of combo boxes
10033       }
10034       SendToProgram(buf, cps);
10035       if(cps->reload) ResendOptions(cps);
10036     } else {
10037       SendToProgram("xboard\n", cps);
10038     }
10039 }
10040
10041 void
10042 TwoMachinesEventIfReady P((void))
10043 {
10044   static int curMess = 0;
10045   if (first.lastPing != first.lastPong || !first.initDone) {
10046     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10047     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10048     return;
10049   }
10050   if (second.lastPing != second.lastPong) {
10051     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10052     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10053     return;
10054   }
10055   DisplayMessage("", ""); curMess = 0;
10056   ThawUI();
10057   TwoMachinesEvent();
10058 }
10059
10060 char *
10061 MakeName (char *template)
10062 {
10063     time_t clock;
10064     struct tm *tm;
10065     static char buf[MSG_SIZ];
10066     char *p = buf;
10067     int i;
10068
10069     clock = time((time_t *)NULL);
10070     tm = localtime(&clock);
10071
10072     while(*p++ = *template++) if(p[-1] == '%') {
10073         switch(*template++) {
10074           case 0:   *p = 0; return buf;
10075           case 'Y': i = tm->tm_year+1900; break;
10076           case 'y': i = tm->tm_year-100; break;
10077           case 'M': i = tm->tm_mon+1; break;
10078           case 'd': i = tm->tm_mday; break;
10079           case 'h': i = tm->tm_hour; break;
10080           case 'm': i = tm->tm_min; break;
10081           case 's': i = tm->tm_sec; break;
10082           default:  i = 0;
10083         }
10084         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10085     }
10086     return buf;
10087 }
10088
10089 int
10090 CountPlayers (char *p)
10091 {
10092     int n = 0;
10093     while(p = strchr(p, '\n')) p++, n++; // count participants
10094     return n;
10095 }
10096
10097 FILE *
10098 WriteTourneyFile (char *results, FILE *f)
10099 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10100     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10101     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10102         // create a file with tournament description
10103         fprintf(f, "-participants {%s}\n", appData.participants);
10104         fprintf(f, "-seedBase %d\n", appData.seedBase);
10105         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10106         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10107         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10108         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10109         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10110         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10111         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10112         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10113         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10114         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10115         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10116         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10117         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10118         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10119         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10120         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10121         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10122         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10123         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10124         fprintf(f, "-smpCores %d\n", appData.smpCores);
10125         if(searchTime > 0)
10126                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10127         else {
10128                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10129                 fprintf(f, "-tc %s\n", appData.timeControl);
10130                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10131         }
10132         fprintf(f, "-results \"%s\"\n", results);
10133     }
10134     return f;
10135 }
10136
10137 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10138
10139 void
10140 Substitute (char *participants, int expunge)
10141 {
10142     int i, changed, changes=0, nPlayers=0;
10143     char *p, *q, *r, buf[MSG_SIZ];
10144     if(participants == NULL) return;
10145     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10146     r = p = participants; q = appData.participants;
10147     while(*p && *p == *q) {
10148         if(*p == '\n') r = p+1, nPlayers++;
10149         p++; q++;
10150     }
10151     if(*p) { // difference
10152         while(*p && *p++ != '\n');
10153         while(*q && *q++ != '\n');
10154       changed = nPlayers;
10155         changes = 1 + (strcmp(p, q) != 0);
10156     }
10157     if(changes == 1) { // a single engine mnemonic was changed
10158         q = r; while(*q) nPlayers += (*q++ == '\n');
10159         p = buf; while(*r && (*p = *r++) != '\n') p++;
10160         *p = NULLCHAR;
10161         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10162         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10163         if(mnemonic[i]) { // The substitute is valid
10164             FILE *f;
10165             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10166                 flock(fileno(f), LOCK_EX);
10167                 ParseArgsFromFile(f);
10168                 fseek(f, 0, SEEK_SET);
10169                 FREE(appData.participants); appData.participants = participants;
10170                 if(expunge) { // erase results of replaced engine
10171                     int len = strlen(appData.results), w, b, dummy;
10172                     for(i=0; i<len; i++) {
10173                         Pairing(i, nPlayers, &w, &b, &dummy);
10174                         if((w == changed || b == changed) && appData.results[i] == '*') {
10175                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10176                             fclose(f);
10177                             return;
10178                         }
10179                     }
10180                     for(i=0; i<len; i++) {
10181                         Pairing(i, nPlayers, &w, &b, &dummy);
10182                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10183                     }
10184                 }
10185                 WriteTourneyFile(appData.results, f);
10186                 fclose(f); // release lock
10187                 return;
10188             }
10189         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10190     }
10191     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10192     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10193     free(participants);
10194     return;
10195 }
10196
10197 int
10198 CheckPlayers (char *participants)
10199 {
10200         int i;
10201         char buf[MSG_SIZ], *p;
10202         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10203         while(p = strchr(participants, '\n')) {
10204             *p = NULLCHAR;
10205             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10206             if(!mnemonic[i]) {
10207                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10208                 *p = '\n';
10209                 DisplayError(buf, 0);
10210                 return 1;
10211             }
10212             *p = '\n';
10213             participants = p + 1;
10214         }
10215         return 0;
10216 }
10217
10218 int
10219 CreateTourney (char *name)
10220 {
10221         FILE *f;
10222         if(matchMode && strcmp(name, appData.tourneyFile)) {
10223              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10224         }
10225         if(name[0] == NULLCHAR) {
10226             if(appData.participants[0])
10227                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10228             return 0;
10229         }
10230         f = fopen(name, "r");
10231         if(f) { // file exists
10232             ASSIGN(appData.tourneyFile, name);
10233             ParseArgsFromFile(f); // parse it
10234         } else {
10235             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10236             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10237                 DisplayError(_("Not enough participants"), 0);
10238                 return 0;
10239             }
10240             if(CheckPlayers(appData.participants)) return 0;
10241             ASSIGN(appData.tourneyFile, name);
10242             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10243             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10244         }
10245         fclose(f);
10246         appData.noChessProgram = FALSE;
10247         appData.clockMode = TRUE;
10248         SetGNUMode();
10249         return 1;
10250 }
10251
10252 int
10253 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10254 {
10255     char buf[MSG_SIZ], *p, *q;
10256     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10257     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10258     skip = !all && group[0]; // if group requested, we start in skip mode
10259     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10260         p = names; q = buf; header = 0;
10261         while(*p && *p != '\n') *q++ = *p++;
10262         *q = 0;
10263         if(*p == '\n') p++;
10264         if(buf[0] == '#') {
10265             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10266             depth++; // we must be entering a new group
10267             if(all) continue; // suppress printing group headers when complete list requested
10268             header = 1;
10269             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10270         }
10271         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10272         if(engineList[i]) free(engineList[i]);
10273         engineList[i] = strdup(buf);
10274         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10275         if(engineMnemonic[i]) free(engineMnemonic[i]);
10276         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10277             strcat(buf, " (");
10278             sscanf(q + 8, "%s", buf + strlen(buf));
10279             strcat(buf, ")");
10280         }
10281         engineMnemonic[i] = strdup(buf);
10282         i++;
10283     }
10284     engineList[i] = engineMnemonic[i] = NULL;
10285     return i;
10286 }
10287
10288 // following implemented as macro to avoid type limitations
10289 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10290
10291 void
10292 SwapEngines (int n)
10293 {   // swap settings for first engine and other engine (so far only some selected options)
10294     int h;
10295     char *p;
10296     if(n == 0) return;
10297     SWAP(directory, p)
10298     SWAP(chessProgram, p)
10299     SWAP(isUCI, h)
10300     SWAP(hasOwnBookUCI, h)
10301     SWAP(protocolVersion, h)
10302     SWAP(reuse, h)
10303     SWAP(scoreIsAbsolute, h)
10304     SWAP(timeOdds, h)
10305     SWAP(logo, p)
10306     SWAP(pgnName, p)
10307     SWAP(pvSAN, h)
10308     SWAP(engOptions, p)
10309     SWAP(engInitString, p)
10310     SWAP(computerString, p)
10311     SWAP(features, p)
10312     SWAP(fenOverride, p)
10313     SWAP(NPS, h)
10314     SWAP(accumulateTC, h)
10315     SWAP(host, p)
10316 }
10317
10318 int
10319 GetEngineLine (char *s, int n)
10320 {
10321     int i;
10322     char buf[MSG_SIZ];
10323     extern char *icsNames;
10324     if(!s || !*s) return 0;
10325     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10326     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10327     if(!mnemonic[i]) return 0;
10328     if(n == 11) return 1; // just testing if there was a match
10329     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10330     if(n == 1) SwapEngines(n);
10331     ParseArgsFromString(buf);
10332     if(n == 1) SwapEngines(n);
10333     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10334         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10335         ParseArgsFromString(buf);
10336     }
10337     return 1;
10338 }
10339
10340 int
10341 SetPlayer (int player, char *p)
10342 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10343     int i;
10344     char buf[MSG_SIZ], *engineName;
10345     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10346     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10347     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10348     if(mnemonic[i]) {
10349         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10350         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10351         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10352         ParseArgsFromString(buf);
10353     } else { // no engine with this nickname is installed!
10354         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10355         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10356         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10357         ModeHighlight();
10358         DisplayError(buf, 0);
10359         return 0;
10360     }
10361     free(engineName);
10362     return i;
10363 }
10364
10365 char *recentEngines;
10366
10367 void
10368 RecentEngineEvent (int nr)
10369 {
10370     int n;
10371 //    SwapEngines(1); // bump first to second
10372 //    ReplaceEngine(&second, 1); // and load it there
10373     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10374     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10375     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10376         ReplaceEngine(&first, 0);
10377         FloatToFront(&appData.recentEngineList, command[n]);
10378     }
10379 }
10380
10381 int
10382 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10383 {   // determine players from game number
10384     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10385
10386     if(appData.tourneyType == 0) {
10387         roundsPerCycle = (nPlayers - 1) | 1;
10388         pairingsPerRound = nPlayers / 2;
10389     } else if(appData.tourneyType > 0) {
10390         roundsPerCycle = nPlayers - appData.tourneyType;
10391         pairingsPerRound = appData.tourneyType;
10392     }
10393     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10394     gamesPerCycle = gamesPerRound * roundsPerCycle;
10395     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10396     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10397     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10398     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10399     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10400     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10401
10402     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10403     if(appData.roundSync) *syncInterval = gamesPerRound;
10404
10405     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10406
10407     if(appData.tourneyType == 0) {
10408         if(curPairing == (nPlayers-1)/2 ) {
10409             *whitePlayer = curRound;
10410             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10411         } else {
10412             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10413             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10414             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10415             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10416         }
10417     } else if(appData.tourneyType > 1) {
10418         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10419         *whitePlayer = curRound + appData.tourneyType;
10420     } else if(appData.tourneyType > 0) {
10421         *whitePlayer = curPairing;
10422         *blackPlayer = curRound + appData.tourneyType;
10423     }
10424
10425     // take care of white/black alternation per round.
10426     // For cycles and games this is already taken care of by default, derived from matchGame!
10427     return curRound & 1;
10428 }
10429
10430 int
10431 NextTourneyGame (int nr, int *swapColors)
10432 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10433     char *p, *q;
10434     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10435     FILE *tf;
10436     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10437     tf = fopen(appData.tourneyFile, "r");
10438     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10439     ParseArgsFromFile(tf); fclose(tf);
10440     InitTimeControls(); // TC might be altered from tourney file
10441
10442     nPlayers = CountPlayers(appData.participants); // count participants
10443     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10444     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10445
10446     if(syncInterval) {
10447         p = q = appData.results;
10448         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10449         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10450             DisplayMessage(_("Waiting for other game(s)"),"");
10451             waitingForGame = TRUE;
10452             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10453             return 0;
10454         }
10455         waitingForGame = FALSE;
10456     }
10457
10458     if(appData.tourneyType < 0) {
10459         if(nr>=0 && !pairingReceived) {
10460             char buf[1<<16];
10461             if(pairing.pr == NoProc) {
10462                 if(!appData.pairingEngine[0]) {
10463                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10464                     return 0;
10465                 }
10466                 StartChessProgram(&pairing); // starts the pairing engine
10467             }
10468             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10469             SendToProgram(buf, &pairing);
10470             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10471             SendToProgram(buf, &pairing);
10472             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10473         }
10474         pairingReceived = 0;                              // ... so we continue here
10475         *swapColors = 0;
10476         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10477         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10478         matchGame = 1; roundNr = nr / syncInterval + 1;
10479     }
10480
10481     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10482
10483     // redefine engines, engine dir, etc.
10484     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10485     if(first.pr == NoProc) {
10486       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10487       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10488     }
10489     if(second.pr == NoProc) {
10490       SwapEngines(1);
10491       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10492       SwapEngines(1);         // and make that valid for second engine by swapping
10493       InitEngine(&second, 1);
10494     }
10495     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10496     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10497     return OK;
10498 }
10499
10500 void
10501 NextMatchGame ()
10502 {   // performs game initialization that does not invoke engines, and then tries to start the game
10503     int res, firstWhite, swapColors = 0;
10504     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10505     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
10506         char buf[MSG_SIZ];
10507         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10508         if(strcmp(buf, currentDebugFile)) { // name has changed
10509             FILE *f = fopen(buf, "w");
10510             if(f) { // if opening the new file failed, just keep using the old one
10511                 ASSIGN(currentDebugFile, buf);
10512                 fclose(debugFP);
10513                 debugFP = f;
10514             }
10515             if(appData.serverFileName) {
10516                 if(serverFP) fclose(serverFP);
10517                 serverFP = fopen(appData.serverFileName, "w");
10518                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10519                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10520             }
10521         }
10522     }
10523     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10524     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10525     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10526     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10527     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10528     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10529     Reset(FALSE, first.pr != NoProc);
10530     res = LoadGameOrPosition(matchGame); // setup game
10531     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10532     if(!res) return; // abort when bad game/pos file
10533     TwoMachinesEvent();
10534 }
10535
10536 void
10537 UserAdjudicationEvent (int result)
10538 {
10539     ChessMove gameResult = GameIsDrawn;
10540
10541     if( result > 0 ) {
10542         gameResult = WhiteWins;
10543     }
10544     else if( result < 0 ) {
10545         gameResult = BlackWins;
10546     }
10547
10548     if( gameMode == TwoMachinesPlay ) {
10549         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10550     }
10551 }
10552
10553
10554 // [HGM] save: calculate checksum of game to make games easily identifiable
10555 int
10556 StringCheckSum (char *s)
10557 {
10558         int i = 0;
10559         if(s==NULL) return 0;
10560         while(*s) i = i*259 + *s++;
10561         return i;
10562 }
10563
10564 int
10565 GameCheckSum ()
10566 {
10567         int i, sum=0;
10568         for(i=backwardMostMove; i<forwardMostMove; i++) {
10569                 sum += pvInfoList[i].depth;
10570                 sum += StringCheckSum(parseList[i]);
10571                 sum += StringCheckSum(commentList[i]);
10572                 sum *= 261;
10573         }
10574         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10575         return sum + StringCheckSum(commentList[i]);
10576 } // end of save patch
10577
10578 void
10579 GameEnds (ChessMove result, char *resultDetails, int whosays)
10580 {
10581     GameMode nextGameMode;
10582     int isIcsGame;
10583     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10584
10585     if(endingGame) return; /* [HGM] crash: forbid recursion */
10586     endingGame = 1;
10587     if(twoBoards) { // [HGM] dual: switch back to one board
10588         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10589         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10590     }
10591     if (appData.debugMode) {
10592       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10593               result, resultDetails ? resultDetails : "(null)", whosays);
10594     }
10595
10596     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10597
10598     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10599
10600     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10601         /* If we are playing on ICS, the server decides when the
10602            game is over, but the engine can offer to draw, claim
10603            a draw, or resign.
10604          */
10605 #if ZIPPY
10606         if (appData.zippyPlay && first.initDone) {
10607             if (result == GameIsDrawn) {
10608                 /* In case draw still needs to be claimed */
10609                 SendToICS(ics_prefix);
10610                 SendToICS("draw\n");
10611             } else if (StrCaseStr(resultDetails, "resign")) {
10612                 SendToICS(ics_prefix);
10613                 SendToICS("resign\n");
10614             }
10615         }
10616 #endif
10617         endingGame = 0; /* [HGM] crash */
10618         return;
10619     }
10620
10621     /* If we're loading the game from a file, stop */
10622     if (whosays == GE_FILE) {
10623       (void) StopLoadGameTimer();
10624       gameFileFP = NULL;
10625     }
10626
10627     /* Cancel draw offers */
10628     first.offeredDraw = second.offeredDraw = 0;
10629
10630     /* If this is an ICS game, only ICS can really say it's done;
10631        if not, anyone can. */
10632     isIcsGame = (gameMode == IcsPlayingWhite ||
10633                  gameMode == IcsPlayingBlack ||
10634                  gameMode == IcsObserving    ||
10635                  gameMode == IcsExamining);
10636
10637     if (!isIcsGame || whosays == GE_ICS) {
10638         /* OK -- not an ICS game, or ICS said it was done */
10639         StopClocks();
10640         if (!isIcsGame && !appData.noChessProgram)
10641           SetUserThinkingEnables();
10642
10643         /* [HGM] if a machine claims the game end we verify this claim */
10644         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10645             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10646                 char claimer;
10647                 ChessMove trueResult = (ChessMove) -1;
10648
10649                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10650                                             first.twoMachinesColor[0] :
10651                                             second.twoMachinesColor[0] ;
10652
10653                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10654                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10655                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10656                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10657                 } else
10658                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10659                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10660                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10661                 } else
10662                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10663                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10664                 }
10665
10666                 // now verify win claims, but not in drop games, as we don't understand those yet
10667                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10668                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10669                     (result == WhiteWins && claimer == 'w' ||
10670                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10671                       if (appData.debugMode) {
10672                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10673                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10674                       }
10675                       if(result != trueResult) {
10676                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10677                               result = claimer == 'w' ? BlackWins : WhiteWins;
10678                               resultDetails = buf;
10679                       }
10680                 } else
10681                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10682                     && (forwardMostMove <= backwardMostMove ||
10683                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10684                         (claimer=='b')==(forwardMostMove&1))
10685                                                                                   ) {
10686                       /* [HGM] verify: draws that were not flagged are false claims */
10687                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10688                       result = claimer == 'w' ? BlackWins : WhiteWins;
10689                       resultDetails = buf;
10690                 }
10691                 /* (Claiming a loss is accepted no questions asked!) */
10692             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10693                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10694                 result = GameUnfinished;
10695                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10696             }
10697             /* [HGM] bare: don't allow bare King to win */
10698             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10699                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10700                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10701                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10702                && result != GameIsDrawn)
10703             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10704                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10705                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10706                         if(p >= 0 && p <= (int)WhiteKing) k++;
10707                 }
10708                 if (appData.debugMode) {
10709                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10710                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10711                 }
10712                 if(k <= 1) {
10713                         result = GameIsDrawn;
10714                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10715                         resultDetails = buf;
10716                 }
10717             }
10718         }
10719
10720
10721         if(serverMoves != NULL && !loadFlag) { char c = '=';
10722             if(result==WhiteWins) c = '+';
10723             if(result==BlackWins) c = '-';
10724             if(resultDetails != NULL)
10725                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10726         }
10727         if (resultDetails != NULL) {
10728             gameInfo.result = result;
10729             gameInfo.resultDetails = StrSave(resultDetails);
10730
10731             /* display last move only if game was not loaded from file */
10732             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10733                 DisplayMove(currentMove - 1);
10734
10735             if (forwardMostMove != 0) {
10736                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10737                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10738                                                                 ) {
10739                     if (*appData.saveGameFile != NULLCHAR) {
10740                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10741                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10742                         else
10743                         SaveGameToFile(appData.saveGameFile, TRUE);
10744                     } else if (appData.autoSaveGames) {
10745                         AutoSaveGame();
10746                     }
10747                     if (*appData.savePositionFile != NULLCHAR) {
10748                         SavePositionToFile(appData.savePositionFile);
10749                     }
10750                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10751                 }
10752             }
10753
10754             /* Tell program how game ended in case it is learning */
10755             /* [HGM] Moved this to after saving the PGN, just in case */
10756             /* engine died and we got here through time loss. In that */
10757             /* case we will get a fatal error writing the pipe, which */
10758             /* would otherwise lose us the PGN.                       */
10759             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10760             /* output during GameEnds should never be fatal anymore   */
10761             if (gameMode == MachinePlaysWhite ||
10762                 gameMode == MachinePlaysBlack ||
10763                 gameMode == TwoMachinesPlay ||
10764                 gameMode == IcsPlayingWhite ||
10765                 gameMode == IcsPlayingBlack ||
10766                 gameMode == BeginningOfGame) {
10767                 char buf[MSG_SIZ];
10768                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10769                         resultDetails);
10770                 if (first.pr != NoProc) {
10771                     SendToProgram(buf, &first);
10772                 }
10773                 if (second.pr != NoProc &&
10774                     gameMode == TwoMachinesPlay) {
10775                     SendToProgram(buf, &second);
10776                 }
10777             }
10778         }
10779
10780         if (appData.icsActive) {
10781             if (appData.quietPlay &&
10782                 (gameMode == IcsPlayingWhite ||
10783                  gameMode == IcsPlayingBlack)) {
10784                 SendToICS(ics_prefix);
10785                 SendToICS("set shout 1\n");
10786             }
10787             nextGameMode = IcsIdle;
10788             ics_user_moved = FALSE;
10789             /* clean up premove.  It's ugly when the game has ended and the
10790              * premove highlights are still on the board.
10791              */
10792             if (gotPremove) {
10793               gotPremove = FALSE;
10794               ClearPremoveHighlights();
10795               DrawPosition(FALSE, boards[currentMove]);
10796             }
10797             if (whosays == GE_ICS) {
10798                 switch (result) {
10799                 case WhiteWins:
10800                     if (gameMode == IcsPlayingWhite)
10801                         PlayIcsWinSound();
10802                     else if(gameMode == IcsPlayingBlack)
10803                         PlayIcsLossSound();
10804                     break;
10805                 case BlackWins:
10806                     if (gameMode == IcsPlayingBlack)
10807                         PlayIcsWinSound();
10808                     else if(gameMode == IcsPlayingWhite)
10809                         PlayIcsLossSound();
10810                     break;
10811                 case GameIsDrawn:
10812                     PlayIcsDrawSound();
10813                     break;
10814                 default:
10815                     PlayIcsUnfinishedSound();
10816                 }
10817             }
10818         } else if (gameMode == EditGame ||
10819                    gameMode == PlayFromGameFile ||
10820                    gameMode == AnalyzeMode ||
10821                    gameMode == AnalyzeFile) {
10822             nextGameMode = gameMode;
10823         } else {
10824             nextGameMode = EndOfGame;
10825         }
10826         pausing = FALSE;
10827         ModeHighlight();
10828     } else {
10829         nextGameMode = gameMode;
10830     }
10831
10832     if (appData.noChessProgram) {
10833         gameMode = nextGameMode;
10834         ModeHighlight();
10835         endingGame = 0; /* [HGM] crash */
10836         return;
10837     }
10838
10839     if (first.reuse) {
10840         /* Put first chess program into idle state */
10841         if (first.pr != NoProc &&
10842             (gameMode == MachinePlaysWhite ||
10843              gameMode == MachinePlaysBlack ||
10844              gameMode == TwoMachinesPlay ||
10845              gameMode == IcsPlayingWhite ||
10846              gameMode == IcsPlayingBlack ||
10847              gameMode == BeginningOfGame)) {
10848             SendToProgram("force\n", &first);
10849             if (first.usePing) {
10850               char buf[MSG_SIZ];
10851               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10852               SendToProgram(buf, &first);
10853             }
10854         }
10855     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10856         /* Kill off first chess program */
10857         if (first.isr != NULL)
10858           RemoveInputSource(first.isr);
10859         first.isr = NULL;
10860
10861         if (first.pr != NoProc) {
10862             ExitAnalyzeMode();
10863             DoSleep( appData.delayBeforeQuit );
10864             SendToProgram("quit\n", &first);
10865             DoSleep( appData.delayAfterQuit );
10866             DestroyChildProcess(first.pr, first.useSigterm);
10867             first.reload = TRUE;
10868         }
10869         first.pr = NoProc;
10870     }
10871     if (second.reuse) {
10872         /* Put second chess program into idle state */
10873         if (second.pr != NoProc &&
10874             gameMode == TwoMachinesPlay) {
10875             SendToProgram("force\n", &second);
10876             if (second.usePing) {
10877               char buf[MSG_SIZ];
10878               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10879               SendToProgram(buf, &second);
10880             }
10881         }
10882     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10883         /* Kill off second chess program */
10884         if (second.isr != NULL)
10885           RemoveInputSource(second.isr);
10886         second.isr = NULL;
10887
10888         if (second.pr != NoProc) {
10889             DoSleep( appData.delayBeforeQuit );
10890             SendToProgram("quit\n", &second);
10891             DoSleep( appData.delayAfterQuit );
10892             DestroyChildProcess(second.pr, second.useSigterm);
10893             second.reload = TRUE;
10894         }
10895         second.pr = NoProc;
10896     }
10897
10898     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10899         char resChar = '=';
10900         switch (result) {
10901         case WhiteWins:
10902           resChar = '+';
10903           if (first.twoMachinesColor[0] == 'w') {
10904             first.matchWins++;
10905           } else {
10906             second.matchWins++;
10907           }
10908           break;
10909         case BlackWins:
10910           resChar = '-';
10911           if (first.twoMachinesColor[0] == 'b') {
10912             first.matchWins++;
10913           } else {
10914             second.matchWins++;
10915           }
10916           break;
10917         case GameUnfinished:
10918           resChar = ' ';
10919         default:
10920           break;
10921         }
10922
10923         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10924         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10925             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10926             ReserveGame(nextGame, resChar); // sets nextGame
10927             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10928             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10929         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10930
10931         if (nextGame <= appData.matchGames && !abortMatch) {
10932             gameMode = nextGameMode;
10933             matchGame = nextGame; // this will be overruled in tourney mode!
10934             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10935             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10936             endingGame = 0; /* [HGM] crash */
10937             return;
10938         } else {
10939             gameMode = nextGameMode;
10940             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10941                      first.tidy, second.tidy,
10942                      first.matchWins, second.matchWins,
10943                      appData.matchGames - (first.matchWins + second.matchWins));
10944             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10945             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10946             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10947             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10948                 first.twoMachinesColor = "black\n";
10949                 second.twoMachinesColor = "white\n";
10950             } else {
10951                 first.twoMachinesColor = "white\n";
10952                 second.twoMachinesColor = "black\n";
10953             }
10954         }
10955     }
10956     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10957         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10958       ExitAnalyzeMode();
10959     gameMode = nextGameMode;
10960     ModeHighlight();
10961     endingGame = 0;  /* [HGM] crash */
10962     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10963         if(matchMode == TRUE) { // match through command line: exit with or without popup
10964             if(ranking) {
10965                 ToNrEvent(forwardMostMove);
10966                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10967                 else ExitEvent(0);
10968             } else DisplayFatalError(buf, 0, 0);
10969         } else { // match through menu; just stop, with or without popup
10970             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10971             ModeHighlight();
10972             if(ranking){
10973                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10974             } else DisplayNote(buf);
10975       }
10976       if(ranking) free(ranking);
10977     }
10978 }
10979
10980 /* Assumes program was just initialized (initString sent).
10981    Leaves program in force mode. */
10982 void
10983 FeedMovesToProgram (ChessProgramState *cps, int upto)
10984 {
10985     int i;
10986
10987     if (appData.debugMode)
10988       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10989               startedFromSetupPosition ? "position and " : "",
10990               backwardMostMove, upto, cps->which);
10991     if(currentlyInitializedVariant != gameInfo.variant) {
10992       char buf[MSG_SIZ];
10993         // [HGM] variantswitch: make engine aware of new variant
10994         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10995                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10996         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10997         SendToProgram(buf, cps);
10998         currentlyInitializedVariant = gameInfo.variant;
10999     }
11000     SendToProgram("force\n", cps);
11001     if (startedFromSetupPosition) {
11002         SendBoard(cps, backwardMostMove);
11003     if (appData.debugMode) {
11004         fprintf(debugFP, "feedMoves\n");
11005     }
11006     }
11007     for (i = backwardMostMove; i < upto; i++) {
11008         SendMoveToProgram(i, cps);
11009     }
11010 }
11011
11012
11013 int
11014 ResurrectChessProgram ()
11015 {
11016      /* The chess program may have exited.
11017         If so, restart it and feed it all the moves made so far. */
11018     static int doInit = 0;
11019
11020     if (appData.noChessProgram) return 1;
11021
11022     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11023         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
11024         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11025         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11026     } else {
11027         if (first.pr != NoProc) return 1;
11028         StartChessProgram(&first);
11029     }
11030     InitChessProgram(&first, FALSE);
11031     FeedMovesToProgram(&first, currentMove);
11032
11033     if (!first.sendTime) {
11034         /* can't tell gnuchess what its clock should read,
11035            so we bow to its notion. */
11036         ResetClocks();
11037         timeRemaining[0][currentMove] = whiteTimeRemaining;
11038         timeRemaining[1][currentMove] = blackTimeRemaining;
11039     }
11040
11041     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11042                 appData.icsEngineAnalyze) && first.analysisSupport) {
11043       SendToProgram("analyze\n", &first);
11044       first.analyzing = TRUE;
11045     }
11046     return 1;
11047 }
11048
11049 /*
11050  * Button procedures
11051  */
11052 void
11053 Reset (int redraw, int init)
11054 {
11055     int i;
11056
11057     if (appData.debugMode) {
11058         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11059                 redraw, init, gameMode);
11060     }
11061     CleanupTail(); // [HGM] vari: delete any stored variations
11062     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11063     pausing = pauseExamInvalid = FALSE;
11064     startedFromSetupPosition = blackPlaysFirst = FALSE;
11065     firstMove = TRUE;
11066     whiteFlag = blackFlag = FALSE;
11067     userOfferedDraw = FALSE;
11068     hintRequested = bookRequested = FALSE;
11069     first.maybeThinking = FALSE;
11070     second.maybeThinking = FALSE;
11071     first.bookSuspend = FALSE; // [HGM] book
11072     second.bookSuspend = FALSE;
11073     thinkOutput[0] = NULLCHAR;
11074     lastHint[0] = NULLCHAR;
11075     ClearGameInfo(&gameInfo);
11076     gameInfo.variant = StringToVariant(appData.variant);
11077     ics_user_moved = ics_clock_paused = FALSE;
11078     ics_getting_history = H_FALSE;
11079     ics_gamenum = -1;
11080     white_holding[0] = black_holding[0] = NULLCHAR;
11081     ClearProgramStats();
11082     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11083
11084     ResetFrontEnd();
11085     ClearHighlights();
11086     flipView = appData.flipView;
11087     ClearPremoveHighlights();
11088     gotPremove = FALSE;
11089     alarmSounded = FALSE;
11090
11091     GameEnds(EndOfFile, NULL, GE_PLAYER);
11092     if(appData.serverMovesName != NULL) {
11093         /* [HGM] prepare to make moves file for broadcasting */
11094         clock_t t = clock();
11095         if(serverMoves != NULL) fclose(serverMoves);
11096         serverMoves = fopen(appData.serverMovesName, "r");
11097         if(serverMoves != NULL) {
11098             fclose(serverMoves);
11099             /* delay 15 sec before overwriting, so all clients can see end */
11100             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11101         }
11102         serverMoves = fopen(appData.serverMovesName, "w");
11103     }
11104
11105     ExitAnalyzeMode();
11106     gameMode = BeginningOfGame;
11107     ModeHighlight();
11108     if(appData.icsActive) gameInfo.variant = VariantNormal;
11109     currentMove = forwardMostMove = backwardMostMove = 0;
11110     MarkTargetSquares(1);
11111     InitPosition(redraw);
11112     for (i = 0; i < MAX_MOVES; i++) {
11113         if (commentList[i] != NULL) {
11114             free(commentList[i]);
11115             commentList[i] = NULL;
11116         }
11117     }
11118     ResetClocks();
11119     timeRemaining[0][0] = whiteTimeRemaining;
11120     timeRemaining[1][0] = blackTimeRemaining;
11121
11122     if (first.pr == NoProc) {
11123         StartChessProgram(&first);
11124     }
11125     if (init) {
11126             InitChessProgram(&first, startedFromSetupPosition);
11127     }
11128     DisplayTitle("");
11129     DisplayMessage("", "");
11130     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11131     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11132     ClearMap();        // [HGM] exclude: invalidate map
11133 }
11134
11135 void
11136 AutoPlayGameLoop ()
11137 {
11138     for (;;) {
11139         if (!AutoPlayOneMove())
11140           return;
11141         if (matchMode || appData.timeDelay == 0)
11142           continue;
11143         if (appData.timeDelay < 0)
11144           return;
11145         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11146         break;
11147     }
11148 }
11149
11150 void
11151 AnalyzeNextGame()
11152 {
11153     ReloadGame(1); // next game
11154 }
11155
11156 int
11157 AutoPlayOneMove ()
11158 {
11159     int fromX, fromY, toX, toY;
11160
11161     if (appData.debugMode) {
11162       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11163     }
11164
11165     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11166       return FALSE;
11167
11168     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11169       pvInfoList[currentMove].depth = programStats.depth;
11170       pvInfoList[currentMove].score = programStats.score;
11171       pvInfoList[currentMove].time  = 0;
11172       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11173     }
11174
11175     if (currentMove >= forwardMostMove) {
11176       if(gameMode == AnalyzeFile) {
11177           if(appData.loadGameIndex == -1) {
11178             GameEnds(EndOfFile, NULL, GE_FILE);
11179           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11180           } else {
11181           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11182         }
11183       }
11184 //      gameMode = EndOfGame;
11185 //      ModeHighlight();
11186
11187       /* [AS] Clear current move marker at the end of a game */
11188       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11189
11190       return FALSE;
11191     }
11192
11193     toX = moveList[currentMove][2] - AAA;
11194     toY = moveList[currentMove][3] - ONE;
11195
11196     if (moveList[currentMove][1] == '@') {
11197         if (appData.highlightLastMove) {
11198             SetHighlights(-1, -1, toX, toY);
11199         }
11200     } else {
11201         fromX = moveList[currentMove][0] - AAA;
11202         fromY = moveList[currentMove][1] - ONE;
11203
11204         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11205
11206         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11207
11208         if (appData.highlightLastMove) {
11209             SetHighlights(fromX, fromY, toX, toY);
11210         }
11211     }
11212     DisplayMove(currentMove);
11213     SendMoveToProgram(currentMove++, &first);
11214     DisplayBothClocks();
11215     DrawPosition(FALSE, boards[currentMove]);
11216     // [HGM] PV info: always display, routine tests if empty
11217     DisplayComment(currentMove - 1, commentList[currentMove]);
11218     return TRUE;
11219 }
11220
11221
11222 int
11223 LoadGameOneMove (ChessMove readAhead)
11224 {
11225     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11226     char promoChar = NULLCHAR;
11227     ChessMove moveType;
11228     char move[MSG_SIZ];
11229     char *p, *q;
11230
11231     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11232         gameMode != AnalyzeMode && gameMode != Training) {
11233         gameFileFP = NULL;
11234         return FALSE;
11235     }
11236
11237     yyboardindex = forwardMostMove;
11238     if (readAhead != EndOfFile) {
11239       moveType = readAhead;
11240     } else {
11241       if (gameFileFP == NULL)
11242           return FALSE;
11243       moveType = (ChessMove) Myylex();
11244     }
11245
11246     done = FALSE;
11247     switch (moveType) {
11248       case Comment:
11249         if (appData.debugMode)
11250           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11251         p = yy_text;
11252
11253         /* append the comment but don't display it */
11254         AppendComment(currentMove, p, FALSE);
11255         return TRUE;
11256
11257       case WhiteCapturesEnPassant:
11258       case BlackCapturesEnPassant:
11259       case WhitePromotion:
11260       case BlackPromotion:
11261       case WhiteNonPromotion:
11262       case BlackNonPromotion:
11263       case NormalMove:
11264       case WhiteKingSideCastle:
11265       case WhiteQueenSideCastle:
11266       case BlackKingSideCastle:
11267       case BlackQueenSideCastle:
11268       case WhiteKingSideCastleWild:
11269       case WhiteQueenSideCastleWild:
11270       case BlackKingSideCastleWild:
11271       case BlackQueenSideCastleWild:
11272       /* PUSH Fabien */
11273       case WhiteHSideCastleFR:
11274       case WhiteASideCastleFR:
11275       case BlackHSideCastleFR:
11276       case BlackASideCastleFR:
11277       /* POP Fabien */
11278         if (appData.debugMode)
11279           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11280         fromX = currentMoveString[0] - AAA;
11281         fromY = currentMoveString[1] - ONE;
11282         toX = currentMoveString[2] - AAA;
11283         toY = currentMoveString[3] - ONE;
11284         promoChar = currentMoveString[4];
11285         break;
11286
11287       case WhiteDrop:
11288       case BlackDrop:
11289         if (appData.debugMode)
11290           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11291         fromX = moveType == WhiteDrop ?
11292           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11293         (int) CharToPiece(ToLower(currentMoveString[0]));
11294         fromY = DROP_RANK;
11295         toX = currentMoveString[2] - AAA;
11296         toY = currentMoveString[3] - ONE;
11297         break;
11298
11299       case WhiteWins:
11300       case BlackWins:
11301       case GameIsDrawn:
11302       case GameUnfinished:
11303         if (appData.debugMode)
11304           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11305         p = strchr(yy_text, '{');
11306         if (p == NULL) p = strchr(yy_text, '(');
11307         if (p == NULL) {
11308             p = yy_text;
11309             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11310         } else {
11311             q = strchr(p, *p == '{' ? '}' : ')');
11312             if (q != NULL) *q = NULLCHAR;
11313             p++;
11314         }
11315         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11316         GameEnds(moveType, p, GE_FILE);
11317         done = TRUE;
11318         if (cmailMsgLoaded) {
11319             ClearHighlights();
11320             flipView = WhiteOnMove(currentMove);
11321             if (moveType == GameUnfinished) flipView = !flipView;
11322             if (appData.debugMode)
11323               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11324         }
11325         break;
11326
11327       case EndOfFile:
11328         if (appData.debugMode)
11329           fprintf(debugFP, "Parser hit end of file\n");
11330         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11331           case MT_NONE:
11332           case MT_CHECK:
11333             break;
11334           case MT_CHECKMATE:
11335           case MT_STAINMATE:
11336             if (WhiteOnMove(currentMove)) {
11337                 GameEnds(BlackWins, "Black mates", GE_FILE);
11338             } else {
11339                 GameEnds(WhiteWins, "White mates", GE_FILE);
11340             }
11341             break;
11342           case MT_STALEMATE:
11343             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11344             break;
11345         }
11346         done = TRUE;
11347         break;
11348
11349       case MoveNumberOne:
11350         if (lastLoadGameStart == GNUChessGame) {
11351             /* GNUChessGames have numbers, but they aren't move numbers */
11352             if (appData.debugMode)
11353               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11354                       yy_text, (int) moveType);
11355             return LoadGameOneMove(EndOfFile); /* tail recursion */
11356         }
11357         /* else fall thru */
11358
11359       case XBoardGame:
11360       case GNUChessGame:
11361       case PGNTag:
11362         /* Reached start of next game in file */
11363         if (appData.debugMode)
11364           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11365         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11366           case MT_NONE:
11367           case MT_CHECK:
11368             break;
11369           case MT_CHECKMATE:
11370           case MT_STAINMATE:
11371             if (WhiteOnMove(currentMove)) {
11372                 GameEnds(BlackWins, "Black mates", GE_FILE);
11373             } else {
11374                 GameEnds(WhiteWins, "White mates", GE_FILE);
11375             }
11376             break;
11377           case MT_STALEMATE:
11378             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11379             break;
11380         }
11381         done = TRUE;
11382         break;
11383
11384       case PositionDiagram:     /* should not happen; ignore */
11385       case ElapsedTime:         /* ignore */
11386       case NAG:                 /* ignore */
11387         if (appData.debugMode)
11388           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11389                   yy_text, (int) moveType);
11390         return LoadGameOneMove(EndOfFile); /* tail recursion */
11391
11392       case IllegalMove:
11393         if (appData.testLegality) {
11394             if (appData.debugMode)
11395               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11396             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11397                     (forwardMostMove / 2) + 1,
11398                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11399             DisplayError(move, 0);
11400             done = TRUE;
11401         } else {
11402             if (appData.debugMode)
11403               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11404                       yy_text, currentMoveString);
11405             fromX = currentMoveString[0] - AAA;
11406             fromY = currentMoveString[1] - ONE;
11407             toX = currentMoveString[2] - AAA;
11408             toY = currentMoveString[3] - ONE;
11409             promoChar = currentMoveString[4];
11410         }
11411         break;
11412
11413       case AmbiguousMove:
11414         if (appData.debugMode)
11415           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11416         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11417                 (forwardMostMove / 2) + 1,
11418                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11419         DisplayError(move, 0);
11420         done = TRUE;
11421         break;
11422
11423       default:
11424       case ImpossibleMove:
11425         if (appData.debugMode)
11426           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11427         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11428                 (forwardMostMove / 2) + 1,
11429                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11430         DisplayError(move, 0);
11431         done = TRUE;
11432         break;
11433     }
11434
11435     if (done) {
11436         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11437             DrawPosition(FALSE, boards[currentMove]);
11438             DisplayBothClocks();
11439             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11440               DisplayComment(currentMove - 1, commentList[currentMove]);
11441         }
11442         (void) StopLoadGameTimer();
11443         gameFileFP = NULL;
11444         cmailOldMove = forwardMostMove;
11445         return FALSE;
11446     } else {
11447         /* currentMoveString is set as a side-effect of yylex */
11448
11449         thinkOutput[0] = NULLCHAR;
11450         MakeMove(fromX, fromY, toX, toY, promoChar);
11451         currentMove = forwardMostMove;
11452         return TRUE;
11453     }
11454 }
11455
11456 /* Load the nth game from the given file */
11457 int
11458 LoadGameFromFile (char *filename, int n, char *title, int useList)
11459 {
11460     FILE *f;
11461     char buf[MSG_SIZ];
11462
11463     if (strcmp(filename, "-") == 0) {
11464         f = stdin;
11465         title = "stdin";
11466     } else {
11467         f = fopen(filename, "rb");
11468         if (f == NULL) {
11469           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11470             DisplayError(buf, errno);
11471             return FALSE;
11472         }
11473     }
11474     if (fseek(f, 0, 0) == -1) {
11475         /* f is not seekable; probably a pipe */
11476         useList = FALSE;
11477     }
11478     if (useList && n == 0) {
11479         int error = GameListBuild(f);
11480         if (error) {
11481             DisplayError(_("Cannot build game list"), error);
11482         } else if (!ListEmpty(&gameList) &&
11483                    ((ListGame *) gameList.tailPred)->number > 1) {
11484             GameListPopUp(f, title);
11485             return TRUE;
11486         }
11487         GameListDestroy();
11488         n = 1;
11489     }
11490     if (n == 0) n = 1;
11491     return LoadGame(f, n, title, FALSE);
11492 }
11493
11494
11495 void
11496 MakeRegisteredMove ()
11497 {
11498     int fromX, fromY, toX, toY;
11499     char promoChar;
11500     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11501         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11502           case CMAIL_MOVE:
11503           case CMAIL_DRAW:
11504             if (appData.debugMode)
11505               fprintf(debugFP, "Restoring %s for game %d\n",
11506                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11507
11508             thinkOutput[0] = NULLCHAR;
11509             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11510             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11511             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11512             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11513             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11514             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11515             MakeMove(fromX, fromY, toX, toY, promoChar);
11516             ShowMove(fromX, fromY, toX, toY);
11517
11518             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11519               case MT_NONE:
11520               case MT_CHECK:
11521                 break;
11522
11523               case MT_CHECKMATE:
11524               case MT_STAINMATE:
11525                 if (WhiteOnMove(currentMove)) {
11526                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11527                 } else {
11528                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11529                 }
11530                 break;
11531
11532               case MT_STALEMATE:
11533                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11534                 break;
11535             }
11536
11537             break;
11538
11539           case CMAIL_RESIGN:
11540             if (WhiteOnMove(currentMove)) {
11541                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11542             } else {
11543                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11544             }
11545             break;
11546
11547           case CMAIL_ACCEPT:
11548             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11549             break;
11550
11551           default:
11552             break;
11553         }
11554     }
11555
11556     return;
11557 }
11558
11559 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11560 int
11561 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11562 {
11563     int retVal;
11564
11565     if (gameNumber > nCmailGames) {
11566         DisplayError(_("No more games in this message"), 0);
11567         return FALSE;
11568     }
11569     if (f == lastLoadGameFP) {
11570         int offset = gameNumber - lastLoadGameNumber;
11571         if (offset == 0) {
11572             cmailMsg[0] = NULLCHAR;
11573             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11574                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11575                 nCmailMovesRegistered--;
11576             }
11577             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11578             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11579                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11580             }
11581         } else {
11582             if (! RegisterMove()) return FALSE;
11583         }
11584     }
11585
11586     retVal = LoadGame(f, gameNumber, title, useList);
11587
11588     /* Make move registered during previous look at this game, if any */
11589     MakeRegisteredMove();
11590
11591     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11592         commentList[currentMove]
11593           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11594         DisplayComment(currentMove - 1, commentList[currentMove]);
11595     }
11596
11597     return retVal;
11598 }
11599
11600 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11601 int
11602 ReloadGame (int offset)
11603 {
11604     int gameNumber = lastLoadGameNumber + offset;
11605     if (lastLoadGameFP == NULL) {
11606         DisplayError(_("No game has been loaded yet"), 0);
11607         return FALSE;
11608     }
11609     if (gameNumber <= 0) {
11610         DisplayError(_("Can't back up any further"), 0);
11611         return FALSE;
11612     }
11613     if (cmailMsgLoaded) {
11614         return CmailLoadGame(lastLoadGameFP, gameNumber,
11615                              lastLoadGameTitle, lastLoadGameUseList);
11616     } else {
11617         return LoadGame(lastLoadGameFP, gameNumber,
11618                         lastLoadGameTitle, lastLoadGameUseList);
11619     }
11620 }
11621
11622 int keys[EmptySquare+1];
11623
11624 int
11625 PositionMatches (Board b1, Board b2)
11626 {
11627     int r, f, sum=0;
11628     switch(appData.searchMode) {
11629         case 1: return CompareWithRights(b1, b2);
11630         case 2:
11631             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11632                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11633             }
11634             return TRUE;
11635         case 3:
11636             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11637               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11638                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11639             }
11640             return sum==0;
11641         case 4:
11642             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11643                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11644             }
11645             return sum==0;
11646     }
11647     return TRUE;
11648 }
11649
11650 #define Q_PROMO  4
11651 #define Q_EP     3
11652 #define Q_BCASTL 2
11653 #define Q_WCASTL 1
11654
11655 int pieceList[256], quickBoard[256];
11656 ChessSquare pieceType[256] = { EmptySquare };
11657 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11658 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11659 int soughtTotal, turn;
11660 Boolean epOK, flipSearch;
11661
11662 typedef struct {
11663     unsigned char piece, to;
11664 } Move;
11665
11666 #define DSIZE (250000)
11667
11668 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11669 Move *moveDatabase = initialSpace;
11670 unsigned int movePtr, dataSize = DSIZE;
11671
11672 int
11673 MakePieceList (Board board, int *counts)
11674 {
11675     int r, f, n=Q_PROMO, total=0;
11676     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11677     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11678         int sq = f + (r<<4);
11679         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11680             quickBoard[sq] = ++n;
11681             pieceList[n] = sq;
11682             pieceType[n] = board[r][f];
11683             counts[board[r][f]]++;
11684             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11685             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11686             total++;
11687         }
11688     }
11689     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11690     return total;
11691 }
11692
11693 void
11694 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11695 {
11696     int sq = fromX + (fromY<<4);
11697     int piece = quickBoard[sq];
11698     quickBoard[sq] = 0;
11699     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11700     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11701         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11702         moveDatabase[movePtr++].piece = Q_WCASTL;
11703         quickBoard[sq] = piece;
11704         piece = quickBoard[from]; quickBoard[from] = 0;
11705         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11706     } else
11707     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11708         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11709         moveDatabase[movePtr++].piece = Q_BCASTL;
11710         quickBoard[sq] = piece;
11711         piece = quickBoard[from]; quickBoard[from] = 0;
11712         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11713     } else
11714     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11715         quickBoard[(fromY<<4)+toX] = 0;
11716         moveDatabase[movePtr].piece = Q_EP;
11717         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11718         moveDatabase[movePtr].to = sq;
11719     } else
11720     if(promoPiece != pieceType[piece]) {
11721         moveDatabase[movePtr++].piece = Q_PROMO;
11722         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11723     }
11724     moveDatabase[movePtr].piece = piece;
11725     quickBoard[sq] = piece;
11726     movePtr++;
11727 }
11728
11729 int
11730 PackGame (Board board)
11731 {
11732     Move *newSpace = NULL;
11733     moveDatabase[movePtr].piece = 0; // terminate previous game
11734     if(movePtr > dataSize) {
11735         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11736         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11737         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11738         if(newSpace) {
11739             int i;
11740             Move *p = moveDatabase, *q = newSpace;
11741             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11742             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11743             moveDatabase = newSpace;
11744         } else { // calloc failed, we must be out of memory. Too bad...
11745             dataSize = 0; // prevent calloc events for all subsequent games
11746             return 0;     // and signal this one isn't cached
11747         }
11748     }
11749     movePtr++;
11750     MakePieceList(board, counts);
11751     return movePtr;
11752 }
11753
11754 int
11755 QuickCompare (Board board, int *minCounts, int *maxCounts)
11756 {   // compare according to search mode
11757     int r, f;
11758     switch(appData.searchMode)
11759     {
11760       case 1: // exact position match
11761         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11762         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11763             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11764         }
11765         break;
11766       case 2: // can have extra material on empty squares
11767         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11768             if(board[r][f] == EmptySquare) continue;
11769             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11770         }
11771         break;
11772       case 3: // material with exact Pawn structure
11773         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11774             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11775             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11776         } // fall through to material comparison
11777       case 4: // exact material
11778         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11779         break;
11780       case 6: // material range with given imbalance
11781         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11782         // fall through to range comparison
11783       case 5: // material range
11784         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11785     }
11786     return TRUE;
11787 }
11788
11789 int
11790 QuickScan (Board board, Move *move)
11791 {   // reconstruct game,and compare all positions in it
11792     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11793     do {
11794         int piece = move->piece;
11795         int to = move->to, from = pieceList[piece];
11796         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11797           if(!piece) return -1;
11798           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11799             piece = (++move)->piece;
11800             from = pieceList[piece];
11801             counts[pieceType[piece]]--;
11802             pieceType[piece] = (ChessSquare) move->to;
11803             counts[move->to]++;
11804           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11805             counts[pieceType[quickBoard[to]]]--;
11806             quickBoard[to] = 0; total--;
11807             move++;
11808             continue;
11809           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11810             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11811             from  = pieceList[piece]; // so this must be King
11812             quickBoard[from] = 0;
11813             pieceList[piece] = to;
11814             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11815             quickBoard[from] = 0; // rook
11816             quickBoard[to] = piece;
11817             to = move->to; piece = move->piece;
11818             goto aftercastle;
11819           }
11820         }
11821         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11822         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11823         quickBoard[from] = 0;
11824       aftercastle:
11825         quickBoard[to] = piece;
11826         pieceList[piece] = to;
11827         cnt++; turn ^= 3;
11828         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11829            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11830            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11831                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11832           ) {
11833             static int lastCounts[EmptySquare+1];
11834             int i;
11835             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11836             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11837         } else stretch = 0;
11838         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11839         move++;
11840     } while(1);
11841 }
11842
11843 void
11844 InitSearch ()
11845 {
11846     int r, f;
11847     flipSearch = FALSE;
11848     CopyBoard(soughtBoard, boards[currentMove]);
11849     soughtTotal = MakePieceList(soughtBoard, maxSought);
11850     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11851     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11852     CopyBoard(reverseBoard, boards[currentMove]);
11853     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11854         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11855         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11856         reverseBoard[r][f] = piece;
11857     }
11858     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11859     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11860     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11861                  || (boards[currentMove][CASTLING][2] == NoRights ||
11862                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11863                  && (boards[currentMove][CASTLING][5] == NoRights ||
11864                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11865       ) {
11866         flipSearch = TRUE;
11867         CopyBoard(flipBoard, soughtBoard);
11868         CopyBoard(rotateBoard, reverseBoard);
11869         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11870             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11871             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11872         }
11873     }
11874     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11875     if(appData.searchMode >= 5) {
11876         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11877         MakePieceList(soughtBoard, minSought);
11878         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11879     }
11880     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11881         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11882 }
11883
11884 GameInfo dummyInfo;
11885 static int creatingBook;
11886
11887 int
11888 GameContainsPosition (FILE *f, ListGame *lg)
11889 {
11890     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11891     int fromX, fromY, toX, toY;
11892     char promoChar;
11893     static int initDone=FALSE;
11894
11895     // weed out games based on numerical tag comparison
11896     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11897     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11898     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11899     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11900     if(!initDone) {
11901         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11902         initDone = TRUE;
11903     }
11904     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11905     else CopyBoard(boards[scratch], initialPosition); // default start position
11906     if(lg->moves) {
11907         turn = btm + 1;
11908         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11909         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11910     }
11911     if(btm) plyNr++;
11912     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11913     fseek(f, lg->offset, 0);
11914     yynewfile(f);
11915     while(1) {
11916         yyboardindex = scratch;
11917         quickFlag = plyNr+1;
11918         next = Myylex();
11919         quickFlag = 0;
11920         switch(next) {
11921             case PGNTag:
11922                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11923             default:
11924                 continue;
11925
11926             case XBoardGame:
11927             case GNUChessGame:
11928                 if(plyNr) return -1; // after we have seen moves, this is for new game
11929               continue;
11930
11931             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11932             case ImpossibleMove:
11933             case WhiteWins: // game ends here with these four
11934             case BlackWins:
11935             case GameIsDrawn:
11936             case GameUnfinished:
11937                 return -1;
11938
11939             case IllegalMove:
11940                 if(appData.testLegality) return -1;
11941             case WhiteCapturesEnPassant:
11942             case BlackCapturesEnPassant:
11943             case WhitePromotion:
11944             case BlackPromotion:
11945             case WhiteNonPromotion:
11946             case BlackNonPromotion:
11947             case NormalMove:
11948             case WhiteKingSideCastle:
11949             case WhiteQueenSideCastle:
11950             case BlackKingSideCastle:
11951             case BlackQueenSideCastle:
11952             case WhiteKingSideCastleWild:
11953             case WhiteQueenSideCastleWild:
11954             case BlackKingSideCastleWild:
11955             case BlackQueenSideCastleWild:
11956             case WhiteHSideCastleFR:
11957             case WhiteASideCastleFR:
11958             case BlackHSideCastleFR:
11959             case BlackASideCastleFR:
11960                 fromX = currentMoveString[0] - AAA;
11961                 fromY = currentMoveString[1] - ONE;
11962                 toX = currentMoveString[2] - AAA;
11963                 toY = currentMoveString[3] - ONE;
11964                 promoChar = currentMoveString[4];
11965                 break;
11966             case WhiteDrop:
11967             case BlackDrop:
11968                 fromX = next == WhiteDrop ?
11969                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11970                   (int) CharToPiece(ToLower(currentMoveString[0]));
11971                 fromY = DROP_RANK;
11972                 toX = currentMoveString[2] - AAA;
11973                 toY = currentMoveString[3] - ONE;
11974                 promoChar = 0;
11975                 break;
11976         }
11977         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11978         plyNr++;
11979         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11980         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11981         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11982         if(appData.findMirror) {
11983             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11984             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11985         }
11986     }
11987 }
11988
11989 /* Load the nth game from open file f */
11990 int
11991 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11992 {
11993     ChessMove cm;
11994     char buf[MSG_SIZ];
11995     int gn = gameNumber;
11996     ListGame *lg = NULL;
11997     int numPGNTags = 0;
11998     int err, pos = -1;
11999     GameMode oldGameMode;
12000     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12001
12002     if (appData.debugMode)
12003         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12004
12005     if (gameMode == Training )
12006         SetTrainingModeOff();
12007
12008     oldGameMode = gameMode;
12009     if (gameMode != BeginningOfGame) {
12010       Reset(FALSE, TRUE);
12011     }
12012
12013     gameFileFP = f;
12014     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12015         fclose(lastLoadGameFP);
12016     }
12017
12018     if (useList) {
12019         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12020
12021         if (lg) {
12022             fseek(f, lg->offset, 0);
12023             GameListHighlight(gameNumber);
12024             pos = lg->position;
12025             gn = 1;
12026         }
12027         else {
12028             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12029               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12030             else
12031             DisplayError(_("Game number out of range"), 0);
12032             return FALSE;
12033         }
12034     } else {
12035         GameListDestroy();
12036         if (fseek(f, 0, 0) == -1) {
12037             if (f == lastLoadGameFP ?
12038                 gameNumber == lastLoadGameNumber + 1 :
12039                 gameNumber == 1) {
12040                 gn = 1;
12041             } else {
12042                 DisplayError(_("Can't seek on game file"), 0);
12043                 return FALSE;
12044             }
12045         }
12046     }
12047     lastLoadGameFP = f;
12048     lastLoadGameNumber = gameNumber;
12049     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12050     lastLoadGameUseList = useList;
12051
12052     yynewfile(f);
12053
12054     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12055       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12056                 lg->gameInfo.black);
12057             DisplayTitle(buf);
12058     } else if (*title != NULLCHAR) {
12059         if (gameNumber > 1) {
12060           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12061             DisplayTitle(buf);
12062         } else {
12063             DisplayTitle(title);
12064         }
12065     }
12066
12067     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12068         gameMode = PlayFromGameFile;
12069         ModeHighlight();
12070     }
12071
12072     currentMove = forwardMostMove = backwardMostMove = 0;
12073     CopyBoard(boards[0], initialPosition);
12074     StopClocks();
12075
12076     /*
12077      * Skip the first gn-1 games in the file.
12078      * Also skip over anything that precedes an identifiable
12079      * start of game marker, to avoid being confused by
12080      * garbage at the start of the file.  Currently
12081      * recognized start of game markers are the move number "1",
12082      * the pattern "gnuchess .* game", the pattern
12083      * "^[#;%] [^ ]* game file", and a PGN tag block.
12084      * A game that starts with one of the latter two patterns
12085      * will also have a move number 1, possibly
12086      * following a position diagram.
12087      * 5-4-02: Let's try being more lenient and allowing a game to
12088      * start with an unnumbered move.  Does that break anything?
12089      */
12090     cm = lastLoadGameStart = EndOfFile;
12091     while (gn > 0) {
12092         yyboardindex = forwardMostMove;
12093         cm = (ChessMove) Myylex();
12094         switch (cm) {
12095           case EndOfFile:
12096             if (cmailMsgLoaded) {
12097                 nCmailGames = CMAIL_MAX_GAMES - gn;
12098             } else {
12099                 Reset(TRUE, TRUE);
12100                 DisplayError(_("Game not found in file"), 0);
12101             }
12102             return FALSE;
12103
12104           case GNUChessGame:
12105           case XBoardGame:
12106             gn--;
12107             lastLoadGameStart = cm;
12108             break;
12109
12110           case MoveNumberOne:
12111             switch (lastLoadGameStart) {
12112               case GNUChessGame:
12113               case XBoardGame:
12114               case PGNTag:
12115                 break;
12116               case MoveNumberOne:
12117               case EndOfFile:
12118                 gn--;           /* count this game */
12119                 lastLoadGameStart = cm;
12120                 break;
12121               default:
12122                 /* impossible */
12123                 break;
12124             }
12125             break;
12126
12127           case PGNTag:
12128             switch (lastLoadGameStart) {
12129               case GNUChessGame:
12130               case PGNTag:
12131               case MoveNumberOne:
12132               case EndOfFile:
12133                 gn--;           /* count this game */
12134                 lastLoadGameStart = cm;
12135                 break;
12136               case XBoardGame:
12137                 lastLoadGameStart = cm; /* game counted already */
12138                 break;
12139               default:
12140                 /* impossible */
12141                 break;
12142             }
12143             if (gn > 0) {
12144                 do {
12145                     yyboardindex = forwardMostMove;
12146                     cm = (ChessMove) Myylex();
12147                 } while (cm == PGNTag || cm == Comment);
12148             }
12149             break;
12150
12151           case WhiteWins:
12152           case BlackWins:
12153           case GameIsDrawn:
12154             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12155                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12156                     != CMAIL_OLD_RESULT) {
12157                     nCmailResults ++ ;
12158                     cmailResult[  CMAIL_MAX_GAMES
12159                                 - gn - 1] = CMAIL_OLD_RESULT;
12160                 }
12161             }
12162             break;
12163
12164           case NormalMove:
12165             /* Only a NormalMove can be at the start of a game
12166              * without a position diagram. */
12167             if (lastLoadGameStart == EndOfFile ) {
12168               gn--;
12169               lastLoadGameStart = MoveNumberOne;
12170             }
12171             break;
12172
12173           default:
12174             break;
12175         }
12176     }
12177
12178     if (appData.debugMode)
12179       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12180
12181     if (cm == XBoardGame) {
12182         /* Skip any header junk before position diagram and/or move 1 */
12183         for (;;) {
12184             yyboardindex = forwardMostMove;
12185             cm = (ChessMove) Myylex();
12186
12187             if (cm == EndOfFile ||
12188                 cm == GNUChessGame || cm == XBoardGame) {
12189                 /* Empty game; pretend end-of-file and handle later */
12190                 cm = EndOfFile;
12191                 break;
12192             }
12193
12194             if (cm == MoveNumberOne || cm == PositionDiagram ||
12195                 cm == PGNTag || cm == Comment)
12196               break;
12197         }
12198     } else if (cm == GNUChessGame) {
12199         if (gameInfo.event != NULL) {
12200             free(gameInfo.event);
12201         }
12202         gameInfo.event = StrSave(yy_text);
12203     }
12204
12205     startedFromSetupPosition = FALSE;
12206     while (cm == PGNTag) {
12207         if (appData.debugMode)
12208           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12209         err = ParsePGNTag(yy_text, &gameInfo);
12210         if (!err) numPGNTags++;
12211
12212         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12213         if(gameInfo.variant != oldVariant) {
12214             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12215             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12216             InitPosition(TRUE);
12217             oldVariant = gameInfo.variant;
12218             if (appData.debugMode)
12219               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12220         }
12221
12222
12223         if (gameInfo.fen != NULL) {
12224           Board initial_position;
12225           startedFromSetupPosition = TRUE;
12226           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12227             Reset(TRUE, TRUE);
12228             DisplayError(_("Bad FEN position in file"), 0);
12229             return FALSE;
12230           }
12231           CopyBoard(boards[0], initial_position);
12232           if (blackPlaysFirst) {
12233             currentMove = forwardMostMove = backwardMostMove = 1;
12234             CopyBoard(boards[1], initial_position);
12235             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12236             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12237             timeRemaining[0][1] = whiteTimeRemaining;
12238             timeRemaining[1][1] = blackTimeRemaining;
12239             if (commentList[0] != NULL) {
12240               commentList[1] = commentList[0];
12241               commentList[0] = NULL;
12242             }
12243           } else {
12244             currentMove = forwardMostMove = backwardMostMove = 0;
12245           }
12246           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12247           {   int i;
12248               initialRulePlies = FENrulePlies;
12249               for( i=0; i< nrCastlingRights; i++ )
12250                   initialRights[i] = initial_position[CASTLING][i];
12251           }
12252           yyboardindex = forwardMostMove;
12253           free(gameInfo.fen);
12254           gameInfo.fen = NULL;
12255         }
12256
12257         yyboardindex = forwardMostMove;
12258         cm = (ChessMove) Myylex();
12259
12260         /* Handle comments interspersed among the tags */
12261         while (cm == Comment) {
12262             char *p;
12263             if (appData.debugMode)
12264               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12265             p = yy_text;
12266             AppendComment(currentMove, p, FALSE);
12267             yyboardindex = forwardMostMove;
12268             cm = (ChessMove) Myylex();
12269         }
12270     }
12271
12272     /* don't rely on existence of Event tag since if game was
12273      * pasted from clipboard the Event tag may not exist
12274      */
12275     if (numPGNTags > 0){
12276         char *tags;
12277         if (gameInfo.variant == VariantNormal) {
12278           VariantClass v = StringToVariant(gameInfo.event);
12279           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12280           if(v < VariantShogi) gameInfo.variant = v;
12281         }
12282         if (!matchMode) {
12283           if( appData.autoDisplayTags ) {
12284             tags = PGNTags(&gameInfo);
12285             TagsPopUp(tags, CmailMsg());
12286             free(tags);
12287           }
12288         }
12289     } else {
12290         /* Make something up, but don't display it now */
12291         SetGameInfo();
12292         TagsPopDown();
12293     }
12294
12295     if (cm == PositionDiagram) {
12296         int i, j;
12297         char *p;
12298         Board initial_position;
12299
12300         if (appData.debugMode)
12301           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12302
12303         if (!startedFromSetupPosition) {
12304             p = yy_text;
12305             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12306               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12307                 switch (*p) {
12308                   case '{':
12309                   case '[':
12310                   case '-':
12311                   case ' ':
12312                   case '\t':
12313                   case '\n':
12314                   case '\r':
12315                     break;
12316                   default:
12317                     initial_position[i][j++] = CharToPiece(*p);
12318                     break;
12319                 }
12320             while (*p == ' ' || *p == '\t' ||
12321                    *p == '\n' || *p == '\r') p++;
12322
12323             if (strncmp(p, "black", strlen("black"))==0)
12324               blackPlaysFirst = TRUE;
12325             else
12326               blackPlaysFirst = FALSE;
12327             startedFromSetupPosition = TRUE;
12328
12329             CopyBoard(boards[0], initial_position);
12330             if (blackPlaysFirst) {
12331                 currentMove = forwardMostMove = backwardMostMove = 1;
12332                 CopyBoard(boards[1], initial_position);
12333                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12334                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12335                 timeRemaining[0][1] = whiteTimeRemaining;
12336                 timeRemaining[1][1] = blackTimeRemaining;
12337                 if (commentList[0] != NULL) {
12338                     commentList[1] = commentList[0];
12339                     commentList[0] = NULL;
12340                 }
12341             } else {
12342                 currentMove = forwardMostMove = backwardMostMove = 0;
12343             }
12344         }
12345         yyboardindex = forwardMostMove;
12346         cm = (ChessMove) Myylex();
12347     }
12348
12349   if(!creatingBook) {
12350     if (first.pr == NoProc) {
12351         StartChessProgram(&first);
12352     }
12353     InitChessProgram(&first, FALSE);
12354     SendToProgram("force\n", &first);
12355     if (startedFromSetupPosition) {
12356         SendBoard(&first, forwardMostMove);
12357     if (appData.debugMode) {
12358         fprintf(debugFP, "Load Game\n");
12359     }
12360         DisplayBothClocks();
12361     }
12362   }
12363
12364     /* [HGM] server: flag to write setup moves in broadcast file as one */
12365     loadFlag = appData.suppressLoadMoves;
12366
12367     while (cm == Comment) {
12368         char *p;
12369         if (appData.debugMode)
12370           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12371         p = yy_text;
12372         AppendComment(currentMove, p, FALSE);
12373         yyboardindex = forwardMostMove;
12374         cm = (ChessMove) Myylex();
12375     }
12376
12377     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12378         cm == WhiteWins || cm == BlackWins ||
12379         cm == GameIsDrawn || cm == GameUnfinished) {
12380         DisplayMessage("", _("No moves in game"));
12381         if (cmailMsgLoaded) {
12382             if (appData.debugMode)
12383               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12384             ClearHighlights();
12385             flipView = FALSE;
12386         }
12387         DrawPosition(FALSE, boards[currentMove]);
12388         DisplayBothClocks();
12389         gameMode = EditGame;
12390         ModeHighlight();
12391         gameFileFP = NULL;
12392         cmailOldMove = 0;
12393         return TRUE;
12394     }
12395
12396     // [HGM] PV info: routine tests if comment empty
12397     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12398         DisplayComment(currentMove - 1, commentList[currentMove]);
12399     }
12400     if (!matchMode && appData.timeDelay != 0)
12401       DrawPosition(FALSE, boards[currentMove]);
12402
12403     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12404       programStats.ok_to_send = 1;
12405     }
12406
12407     /* if the first token after the PGN tags is a move
12408      * and not move number 1, retrieve it from the parser
12409      */
12410     if (cm != MoveNumberOne)
12411         LoadGameOneMove(cm);
12412
12413     /* load the remaining moves from the file */
12414     while (LoadGameOneMove(EndOfFile)) {
12415       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12416       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12417     }
12418
12419     /* rewind to the start of the game */
12420     currentMove = backwardMostMove;
12421
12422     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12423
12424     if (oldGameMode == AnalyzeFile ||
12425         oldGameMode == AnalyzeMode) {
12426       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12427       AnalyzeFileEvent();
12428     }
12429
12430     if(creatingBook) return TRUE;
12431     if (!matchMode && pos > 0) {
12432         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12433     } else
12434     if (matchMode || appData.timeDelay == 0) {
12435       ToEndEvent();
12436     } else if (appData.timeDelay > 0) {
12437       AutoPlayGameLoop();
12438     }
12439
12440     if (appData.debugMode)
12441         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12442
12443     loadFlag = 0; /* [HGM] true game starts */
12444     return TRUE;
12445 }
12446
12447 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12448 int
12449 ReloadPosition (int offset)
12450 {
12451     int positionNumber = lastLoadPositionNumber + offset;
12452     if (lastLoadPositionFP == NULL) {
12453         DisplayError(_("No position has been loaded yet"), 0);
12454         return FALSE;
12455     }
12456     if (positionNumber <= 0) {
12457         DisplayError(_("Can't back up any further"), 0);
12458         return FALSE;
12459     }
12460     return LoadPosition(lastLoadPositionFP, positionNumber,
12461                         lastLoadPositionTitle);
12462 }
12463
12464 /* Load the nth position from the given file */
12465 int
12466 LoadPositionFromFile (char *filename, int n, char *title)
12467 {
12468     FILE *f;
12469     char buf[MSG_SIZ];
12470
12471     if (strcmp(filename, "-") == 0) {
12472         return LoadPosition(stdin, n, "stdin");
12473     } else {
12474         f = fopen(filename, "rb");
12475         if (f == NULL) {
12476             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12477             DisplayError(buf, errno);
12478             return FALSE;
12479         } else {
12480             return LoadPosition(f, n, title);
12481         }
12482     }
12483 }
12484
12485 /* Load the nth position from the given open file, and close it */
12486 int
12487 LoadPosition (FILE *f, int positionNumber, char *title)
12488 {
12489     char *p, line[MSG_SIZ];
12490     Board initial_position;
12491     int i, j, fenMode, pn;
12492
12493     if (gameMode == Training )
12494         SetTrainingModeOff();
12495
12496     if (gameMode != BeginningOfGame) {
12497         Reset(FALSE, TRUE);
12498     }
12499     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12500         fclose(lastLoadPositionFP);
12501     }
12502     if (positionNumber == 0) positionNumber = 1;
12503     lastLoadPositionFP = f;
12504     lastLoadPositionNumber = positionNumber;
12505     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12506     if (first.pr == NoProc && !appData.noChessProgram) {
12507       StartChessProgram(&first);
12508       InitChessProgram(&first, FALSE);
12509     }
12510     pn = positionNumber;
12511     if (positionNumber < 0) {
12512         /* Negative position number means to seek to that byte offset */
12513         if (fseek(f, -positionNumber, 0) == -1) {
12514             DisplayError(_("Can't seek on position file"), 0);
12515             return FALSE;
12516         };
12517         pn = 1;
12518     } else {
12519         if (fseek(f, 0, 0) == -1) {
12520             if (f == lastLoadPositionFP ?
12521                 positionNumber == lastLoadPositionNumber + 1 :
12522                 positionNumber == 1) {
12523                 pn = 1;
12524             } else {
12525                 DisplayError(_("Can't seek on position file"), 0);
12526                 return FALSE;
12527             }
12528         }
12529     }
12530     /* See if this file is FEN or old-style xboard */
12531     if (fgets(line, MSG_SIZ, f) == NULL) {
12532         DisplayError(_("Position not found in file"), 0);
12533         return FALSE;
12534     }
12535     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12536     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12537
12538     if (pn >= 2) {
12539         if (fenMode || line[0] == '#') pn--;
12540         while (pn > 0) {
12541             /* skip positions before number pn */
12542             if (fgets(line, MSG_SIZ, f) == NULL) {
12543                 Reset(TRUE, TRUE);
12544                 DisplayError(_("Position not found in file"), 0);
12545                 return FALSE;
12546             }
12547             if (fenMode || line[0] == '#') pn--;
12548         }
12549     }
12550
12551     if (fenMode) {
12552         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12553             DisplayError(_("Bad FEN position in file"), 0);
12554             return FALSE;
12555         }
12556     } else {
12557         (void) fgets(line, MSG_SIZ, f);
12558         (void) fgets(line, MSG_SIZ, f);
12559
12560         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12561             (void) fgets(line, MSG_SIZ, f);
12562             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12563                 if (*p == ' ')
12564                   continue;
12565                 initial_position[i][j++] = CharToPiece(*p);
12566             }
12567         }
12568
12569         blackPlaysFirst = FALSE;
12570         if (!feof(f)) {
12571             (void) fgets(line, MSG_SIZ, f);
12572             if (strncmp(line, "black", strlen("black"))==0)
12573               blackPlaysFirst = TRUE;
12574         }
12575     }
12576     startedFromSetupPosition = TRUE;
12577
12578     CopyBoard(boards[0], initial_position);
12579     if (blackPlaysFirst) {
12580         currentMove = forwardMostMove = backwardMostMove = 1;
12581         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12582         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12583         CopyBoard(boards[1], initial_position);
12584         DisplayMessage("", _("Black to play"));
12585     } else {
12586         currentMove = forwardMostMove = backwardMostMove = 0;
12587         DisplayMessage("", _("White to play"));
12588     }
12589     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12590     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12591         SendToProgram("force\n", &first);
12592         SendBoard(&first, forwardMostMove);
12593     }
12594     if (appData.debugMode) {
12595 int i, j;
12596   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12597   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12598         fprintf(debugFP, "Load Position\n");
12599     }
12600
12601     if (positionNumber > 1) {
12602       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12603         DisplayTitle(line);
12604     } else {
12605         DisplayTitle(title);
12606     }
12607     gameMode = EditGame;
12608     ModeHighlight();
12609     ResetClocks();
12610     timeRemaining[0][1] = whiteTimeRemaining;
12611     timeRemaining[1][1] = blackTimeRemaining;
12612     DrawPosition(FALSE, boards[currentMove]);
12613
12614     return TRUE;
12615 }
12616
12617
12618 void
12619 CopyPlayerNameIntoFileName (char **dest, char *src)
12620 {
12621     while (*src != NULLCHAR && *src != ',') {
12622         if (*src == ' ') {
12623             *(*dest)++ = '_';
12624             src++;
12625         } else {
12626             *(*dest)++ = *src++;
12627         }
12628     }
12629 }
12630
12631 char *
12632 DefaultFileName (char *ext)
12633 {
12634     static char def[MSG_SIZ];
12635     char *p;
12636
12637     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12638         p = def;
12639         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12640         *p++ = '-';
12641         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12642         *p++ = '.';
12643         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12644     } else {
12645         def[0] = NULLCHAR;
12646     }
12647     return def;
12648 }
12649
12650 /* Save the current game to the given file */
12651 int
12652 SaveGameToFile (char *filename, int append)
12653 {
12654     FILE *f;
12655     char buf[MSG_SIZ];
12656     int result, i, t,tot=0;
12657
12658     if (strcmp(filename, "-") == 0) {
12659         return SaveGame(stdout, 0, NULL);
12660     } else {
12661         for(i=0; i<10; i++) { // upto 10 tries
12662              f = fopen(filename, append ? "a" : "w");
12663              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12664              if(f || errno != 13) break;
12665              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12666              tot += t;
12667         }
12668         if (f == NULL) {
12669             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12670             DisplayError(buf, errno);
12671             return FALSE;
12672         } else {
12673             safeStrCpy(buf, lastMsg, MSG_SIZ);
12674             DisplayMessage(_("Waiting for access to save file"), "");
12675             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12676             DisplayMessage(_("Saving game"), "");
12677             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12678             result = SaveGame(f, 0, NULL);
12679             DisplayMessage(buf, "");
12680             return result;
12681         }
12682     }
12683 }
12684
12685 char *
12686 SavePart (char *str)
12687 {
12688     static char buf[MSG_SIZ];
12689     char *p;
12690
12691     p = strchr(str, ' ');
12692     if (p == NULL) return str;
12693     strncpy(buf, str, p - str);
12694     buf[p - str] = NULLCHAR;
12695     return buf;
12696 }
12697
12698 #define PGN_MAX_LINE 75
12699
12700 #define PGN_SIDE_WHITE  0
12701 #define PGN_SIDE_BLACK  1
12702
12703 static int
12704 FindFirstMoveOutOfBook (int side)
12705 {
12706     int result = -1;
12707
12708     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12709         int index = backwardMostMove;
12710         int has_book_hit = 0;
12711
12712         if( (index % 2) != side ) {
12713             index++;
12714         }
12715
12716         while( index < forwardMostMove ) {
12717             /* Check to see if engine is in book */
12718             int depth = pvInfoList[index].depth;
12719             int score = pvInfoList[index].score;
12720             int in_book = 0;
12721
12722             if( depth <= 2 ) {
12723                 in_book = 1;
12724             }
12725             else if( score == 0 && depth == 63 ) {
12726                 in_book = 1; /* Zappa */
12727             }
12728             else if( score == 2 && depth == 99 ) {
12729                 in_book = 1; /* Abrok */
12730             }
12731
12732             has_book_hit += in_book;
12733
12734             if( ! in_book ) {
12735                 result = index;
12736
12737                 break;
12738             }
12739
12740             index += 2;
12741         }
12742     }
12743
12744     return result;
12745 }
12746
12747 void
12748 GetOutOfBookInfo (char * buf)
12749 {
12750     int oob[2];
12751     int i;
12752     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12753
12754     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12755     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12756
12757     *buf = '\0';
12758
12759     if( oob[0] >= 0 || oob[1] >= 0 ) {
12760         for( i=0; i<2; i++ ) {
12761             int idx = oob[i];
12762
12763             if( idx >= 0 ) {
12764                 if( i > 0 && oob[0] >= 0 ) {
12765                     strcat( buf, "   " );
12766                 }
12767
12768                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12769                 sprintf( buf+strlen(buf), "%s%.2f",
12770                     pvInfoList[idx].score >= 0 ? "+" : "",
12771                     pvInfoList[idx].score / 100.0 );
12772             }
12773         }
12774     }
12775 }
12776
12777 /* Save game in PGN style and close the file */
12778 int
12779 SaveGamePGN (FILE *f)
12780 {
12781     int i, offset, linelen, newblock;
12782 //    char *movetext;
12783     char numtext[32];
12784     int movelen, numlen, blank;
12785     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12786
12787     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12788
12789     PrintPGNTags(f, &gameInfo);
12790
12791     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12792
12793     if (backwardMostMove > 0 || startedFromSetupPosition) {
12794         char *fen = PositionToFEN(backwardMostMove, NULL);
12795         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12796         fprintf(f, "\n{--------------\n");
12797         PrintPosition(f, backwardMostMove);
12798         fprintf(f, "--------------}\n");
12799         free(fen);
12800     }
12801     else {
12802         /* [AS] Out of book annotation */
12803         if( appData.saveOutOfBookInfo ) {
12804             char buf[64];
12805
12806             GetOutOfBookInfo( buf );
12807
12808             if( buf[0] != '\0' ) {
12809                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12810             }
12811         }
12812
12813         fprintf(f, "\n");
12814     }
12815
12816     i = backwardMostMove;
12817     linelen = 0;
12818     newblock = TRUE;
12819
12820     while (i < forwardMostMove) {
12821         /* Print comments preceding this move */
12822         if (commentList[i] != NULL) {
12823             if (linelen > 0) fprintf(f, "\n");
12824             fprintf(f, "%s", commentList[i]);
12825             linelen = 0;
12826             newblock = TRUE;
12827         }
12828
12829         /* Format move number */
12830         if ((i % 2) == 0)
12831           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12832         else
12833           if (newblock)
12834             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12835           else
12836             numtext[0] = NULLCHAR;
12837
12838         numlen = strlen(numtext);
12839         newblock = FALSE;
12840
12841         /* Print move number */
12842         blank = linelen > 0 && numlen > 0;
12843         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12844             fprintf(f, "\n");
12845             linelen = 0;
12846             blank = 0;
12847         }
12848         if (blank) {
12849             fprintf(f, " ");
12850             linelen++;
12851         }
12852         fprintf(f, "%s", numtext);
12853         linelen += numlen;
12854
12855         /* Get move */
12856         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12857         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12858
12859         /* Print move */
12860         blank = linelen > 0 && movelen > 0;
12861         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12862             fprintf(f, "\n");
12863             linelen = 0;
12864             blank = 0;
12865         }
12866         if (blank) {
12867             fprintf(f, " ");
12868             linelen++;
12869         }
12870         fprintf(f, "%s", move_buffer);
12871         linelen += movelen;
12872
12873         /* [AS] Add PV info if present */
12874         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12875             /* [HGM] add time */
12876             char buf[MSG_SIZ]; int seconds;
12877
12878             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12879
12880             if( seconds <= 0)
12881               buf[0] = 0;
12882             else
12883               if( seconds < 30 )
12884                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12885               else
12886                 {
12887                   seconds = (seconds + 4)/10; // round to full seconds
12888                   if( seconds < 60 )
12889                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12890                   else
12891                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12892                 }
12893
12894             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12895                       pvInfoList[i].score >= 0 ? "+" : "",
12896                       pvInfoList[i].score / 100.0,
12897                       pvInfoList[i].depth,
12898                       buf );
12899
12900             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12901
12902             /* Print score/depth */
12903             blank = linelen > 0 && movelen > 0;
12904             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12905                 fprintf(f, "\n");
12906                 linelen = 0;
12907                 blank = 0;
12908             }
12909             if (blank) {
12910                 fprintf(f, " ");
12911                 linelen++;
12912             }
12913             fprintf(f, "%s", move_buffer);
12914             linelen += movelen;
12915         }
12916
12917         i++;
12918     }
12919
12920     /* Start a new line */
12921     if (linelen > 0) fprintf(f, "\n");
12922
12923     /* Print comments after last move */
12924     if (commentList[i] != NULL) {
12925         fprintf(f, "%s\n", commentList[i]);
12926     }
12927
12928     /* Print result */
12929     if (gameInfo.resultDetails != NULL &&
12930         gameInfo.resultDetails[0] != NULLCHAR) {
12931         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12932                 PGNResult(gameInfo.result));
12933     } else {
12934         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12935     }
12936
12937     fclose(f);
12938     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12939     return TRUE;
12940 }
12941
12942 /* Save game in old style and close the file */
12943 int
12944 SaveGameOldStyle (FILE *f)
12945 {
12946     int i, offset;
12947     time_t tm;
12948
12949     tm = time((time_t *) NULL);
12950
12951     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12952     PrintOpponents(f);
12953
12954     if (backwardMostMove > 0 || startedFromSetupPosition) {
12955         fprintf(f, "\n[--------------\n");
12956         PrintPosition(f, backwardMostMove);
12957         fprintf(f, "--------------]\n");
12958     } else {
12959         fprintf(f, "\n");
12960     }
12961
12962     i = backwardMostMove;
12963     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12964
12965     while (i < forwardMostMove) {
12966         if (commentList[i] != NULL) {
12967             fprintf(f, "[%s]\n", commentList[i]);
12968         }
12969
12970         if ((i % 2) == 1) {
12971             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12972             i++;
12973         } else {
12974             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12975             i++;
12976             if (commentList[i] != NULL) {
12977                 fprintf(f, "\n");
12978                 continue;
12979             }
12980             if (i >= forwardMostMove) {
12981                 fprintf(f, "\n");
12982                 break;
12983             }
12984             fprintf(f, "%s\n", parseList[i]);
12985             i++;
12986         }
12987     }
12988
12989     if (commentList[i] != NULL) {
12990         fprintf(f, "[%s]\n", commentList[i]);
12991     }
12992
12993     /* This isn't really the old style, but it's close enough */
12994     if (gameInfo.resultDetails != NULL &&
12995         gameInfo.resultDetails[0] != NULLCHAR) {
12996         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12997                 gameInfo.resultDetails);
12998     } else {
12999         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13000     }
13001
13002     fclose(f);
13003     return TRUE;
13004 }
13005
13006 /* Save the current game to open file f and close the file */
13007 int
13008 SaveGame (FILE *f, int dummy, char *dummy2)
13009 {
13010     if (gameMode == EditPosition) EditPositionDone(TRUE);
13011     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13012     if (appData.oldSaveStyle)
13013       return SaveGameOldStyle(f);
13014     else
13015       return SaveGamePGN(f);
13016 }
13017
13018 /* Save the current position to the given file */
13019 int
13020 SavePositionToFile (char *filename)
13021 {
13022     FILE *f;
13023     char buf[MSG_SIZ];
13024
13025     if (strcmp(filename, "-") == 0) {
13026         return SavePosition(stdout, 0, NULL);
13027     } else {
13028         f = fopen(filename, "a");
13029         if (f == NULL) {
13030             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13031             DisplayError(buf, errno);
13032             return FALSE;
13033         } else {
13034             safeStrCpy(buf, lastMsg, MSG_SIZ);
13035             DisplayMessage(_("Waiting for access to save file"), "");
13036             flock(fileno(f), LOCK_EX); // [HGM] lock
13037             DisplayMessage(_("Saving position"), "");
13038             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13039             SavePosition(f, 0, NULL);
13040             DisplayMessage(buf, "");
13041             return TRUE;
13042         }
13043     }
13044 }
13045
13046 /* Save the current position to the given open file and close the file */
13047 int
13048 SavePosition (FILE *f, int dummy, char *dummy2)
13049 {
13050     time_t tm;
13051     char *fen;
13052
13053     if (gameMode == EditPosition) EditPositionDone(TRUE);
13054     if (appData.oldSaveStyle) {
13055         tm = time((time_t *) NULL);
13056
13057         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13058         PrintOpponents(f);
13059         fprintf(f, "[--------------\n");
13060         PrintPosition(f, currentMove);
13061         fprintf(f, "--------------]\n");
13062     } else {
13063         fen = PositionToFEN(currentMove, NULL);
13064         fprintf(f, "%s\n", fen);
13065         free(fen);
13066     }
13067     fclose(f);
13068     return TRUE;
13069 }
13070
13071 void
13072 ReloadCmailMsgEvent (int unregister)
13073 {
13074 #if !WIN32
13075     static char *inFilename = NULL;
13076     static char *outFilename;
13077     int i;
13078     struct stat inbuf, outbuf;
13079     int status;
13080
13081     /* Any registered moves are unregistered if unregister is set, */
13082     /* i.e. invoked by the signal handler */
13083     if (unregister) {
13084         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13085             cmailMoveRegistered[i] = FALSE;
13086             if (cmailCommentList[i] != NULL) {
13087                 free(cmailCommentList[i]);
13088                 cmailCommentList[i] = NULL;
13089             }
13090         }
13091         nCmailMovesRegistered = 0;
13092     }
13093
13094     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13095         cmailResult[i] = CMAIL_NOT_RESULT;
13096     }
13097     nCmailResults = 0;
13098
13099     if (inFilename == NULL) {
13100         /* Because the filenames are static they only get malloced once  */
13101         /* and they never get freed                                      */
13102         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13103         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13104
13105         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13106         sprintf(outFilename, "%s.out", appData.cmailGameName);
13107     }
13108
13109     status = stat(outFilename, &outbuf);
13110     if (status < 0) {
13111         cmailMailedMove = FALSE;
13112     } else {
13113         status = stat(inFilename, &inbuf);
13114         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13115     }
13116
13117     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13118        counts the games, notes how each one terminated, etc.
13119
13120        It would be nice to remove this kludge and instead gather all
13121        the information while building the game list.  (And to keep it
13122        in the game list nodes instead of having a bunch of fixed-size
13123        parallel arrays.)  Note this will require getting each game's
13124        termination from the PGN tags, as the game list builder does
13125        not process the game moves.  --mann
13126        */
13127     cmailMsgLoaded = TRUE;
13128     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13129
13130     /* Load first game in the file or popup game menu */
13131     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13132
13133 #endif /* !WIN32 */
13134     return;
13135 }
13136
13137 int
13138 RegisterMove ()
13139 {
13140     FILE *f;
13141     char string[MSG_SIZ];
13142
13143     if (   cmailMailedMove
13144         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13145         return TRUE;            /* Allow free viewing  */
13146     }
13147
13148     /* Unregister move to ensure that we don't leave RegisterMove        */
13149     /* with the move registered when the conditions for registering no   */
13150     /* longer hold                                                       */
13151     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13152         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13153         nCmailMovesRegistered --;
13154
13155         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13156           {
13157               free(cmailCommentList[lastLoadGameNumber - 1]);
13158               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13159           }
13160     }
13161
13162     if (cmailOldMove == -1) {
13163         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13164         return FALSE;
13165     }
13166
13167     if (currentMove > cmailOldMove + 1) {
13168         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13169         return FALSE;
13170     }
13171
13172     if (currentMove < cmailOldMove) {
13173         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13174         return FALSE;
13175     }
13176
13177     if (forwardMostMove > currentMove) {
13178         /* Silently truncate extra moves */
13179         TruncateGame();
13180     }
13181
13182     if (   (currentMove == cmailOldMove + 1)
13183         || (   (currentMove == cmailOldMove)
13184             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13185                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13186         if (gameInfo.result != GameUnfinished) {
13187             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13188         }
13189
13190         if (commentList[currentMove] != NULL) {
13191             cmailCommentList[lastLoadGameNumber - 1]
13192               = StrSave(commentList[currentMove]);
13193         }
13194         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13195
13196         if (appData.debugMode)
13197           fprintf(debugFP, "Saving %s for game %d\n",
13198                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13199
13200         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13201
13202         f = fopen(string, "w");
13203         if (appData.oldSaveStyle) {
13204             SaveGameOldStyle(f); /* also closes the file */
13205
13206             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13207             f = fopen(string, "w");
13208             SavePosition(f, 0, NULL); /* also closes the file */
13209         } else {
13210             fprintf(f, "{--------------\n");
13211             PrintPosition(f, currentMove);
13212             fprintf(f, "--------------}\n\n");
13213
13214             SaveGame(f, 0, NULL); /* also closes the file*/
13215         }
13216
13217         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13218         nCmailMovesRegistered ++;
13219     } else if (nCmailGames == 1) {
13220         DisplayError(_("You have not made a move yet"), 0);
13221         return FALSE;
13222     }
13223
13224     return TRUE;
13225 }
13226
13227 void
13228 MailMoveEvent ()
13229 {
13230 #if !WIN32
13231     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13232     FILE *commandOutput;
13233     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13234     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13235     int nBuffers;
13236     int i;
13237     int archived;
13238     char *arcDir;
13239
13240     if (! cmailMsgLoaded) {
13241         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13242         return;
13243     }
13244
13245     if (nCmailGames == nCmailResults) {
13246         DisplayError(_("No unfinished games"), 0);
13247         return;
13248     }
13249
13250 #if CMAIL_PROHIBIT_REMAIL
13251     if (cmailMailedMove) {
13252       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);
13253         DisplayError(msg, 0);
13254         return;
13255     }
13256 #endif
13257
13258     if (! (cmailMailedMove || RegisterMove())) return;
13259
13260     if (   cmailMailedMove
13261         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13262       snprintf(string, MSG_SIZ, partCommandString,
13263                appData.debugMode ? " -v" : "", appData.cmailGameName);
13264         commandOutput = popen(string, "r");
13265
13266         if (commandOutput == NULL) {
13267             DisplayError(_("Failed to invoke cmail"), 0);
13268         } else {
13269             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13270                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13271             }
13272             if (nBuffers > 1) {
13273                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13274                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13275                 nBytes = MSG_SIZ - 1;
13276             } else {
13277                 (void) memcpy(msg, buffer, nBytes);
13278             }
13279             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13280
13281             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13282                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13283
13284                 archived = TRUE;
13285                 for (i = 0; i < nCmailGames; i ++) {
13286                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13287                         archived = FALSE;
13288                     }
13289                 }
13290                 if (   archived
13291                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13292                         != NULL)) {
13293                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13294                            arcDir,
13295                            appData.cmailGameName,
13296                            gameInfo.date);
13297                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13298                     cmailMsgLoaded = FALSE;
13299                 }
13300             }
13301
13302             DisplayInformation(msg);
13303             pclose(commandOutput);
13304         }
13305     } else {
13306         if ((*cmailMsg) != '\0') {
13307             DisplayInformation(cmailMsg);
13308         }
13309     }
13310
13311     return;
13312 #endif /* !WIN32 */
13313 }
13314
13315 char *
13316 CmailMsg ()
13317 {
13318 #if WIN32
13319     return NULL;
13320 #else
13321     int  prependComma = 0;
13322     char number[5];
13323     char string[MSG_SIZ];       /* Space for game-list */
13324     int  i;
13325
13326     if (!cmailMsgLoaded) return "";
13327
13328     if (cmailMailedMove) {
13329       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13330     } else {
13331         /* Create a list of games left */
13332       snprintf(string, MSG_SIZ, "[");
13333         for (i = 0; i < nCmailGames; i ++) {
13334             if (! (   cmailMoveRegistered[i]
13335                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13336                 if (prependComma) {
13337                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13338                 } else {
13339                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13340                     prependComma = 1;
13341                 }
13342
13343                 strcat(string, number);
13344             }
13345         }
13346         strcat(string, "]");
13347
13348         if (nCmailMovesRegistered + nCmailResults == 0) {
13349             switch (nCmailGames) {
13350               case 1:
13351                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13352                 break;
13353
13354               case 2:
13355                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13356                 break;
13357
13358               default:
13359                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13360                          nCmailGames);
13361                 break;
13362             }
13363         } else {
13364             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13365               case 1:
13366                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13367                          string);
13368                 break;
13369
13370               case 0:
13371                 if (nCmailResults == nCmailGames) {
13372                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13373                 } else {
13374                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13375                 }
13376                 break;
13377
13378               default:
13379                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13380                          string);
13381             }
13382         }
13383     }
13384     return cmailMsg;
13385 #endif /* WIN32 */
13386 }
13387
13388 void
13389 ResetGameEvent ()
13390 {
13391     if (gameMode == Training)
13392       SetTrainingModeOff();
13393
13394     Reset(TRUE, TRUE);
13395     cmailMsgLoaded = FALSE;
13396     if (appData.icsActive) {
13397       SendToICS(ics_prefix);
13398       SendToICS("refresh\n");
13399     }
13400 }
13401
13402 void
13403 ExitEvent (int status)
13404 {
13405     exiting++;
13406     if (exiting > 2) {
13407       /* Give up on clean exit */
13408       exit(status);
13409     }
13410     if (exiting > 1) {
13411       /* Keep trying for clean exit */
13412       return;
13413     }
13414
13415     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13416
13417     if (telnetISR != NULL) {
13418       RemoveInputSource(telnetISR);
13419     }
13420     if (icsPR != NoProc) {
13421       DestroyChildProcess(icsPR, TRUE);
13422     }
13423
13424     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13425     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13426
13427     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13428     /* make sure this other one finishes before killing it!                  */
13429     if(endingGame) { int count = 0;
13430         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13431         while(endingGame && count++ < 10) DoSleep(1);
13432         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13433     }
13434
13435     /* Kill off chess programs */
13436     if (first.pr != NoProc) {
13437         ExitAnalyzeMode();
13438
13439         DoSleep( appData.delayBeforeQuit );
13440         SendToProgram("quit\n", &first);
13441         DoSleep( appData.delayAfterQuit );
13442         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13443     }
13444     if (second.pr != NoProc) {
13445         DoSleep( appData.delayBeforeQuit );
13446         SendToProgram("quit\n", &second);
13447         DoSleep( appData.delayAfterQuit );
13448         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13449     }
13450     if (first.isr != NULL) {
13451         RemoveInputSource(first.isr);
13452     }
13453     if (second.isr != NULL) {
13454         RemoveInputSource(second.isr);
13455     }
13456
13457     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13458     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13459
13460     ShutDownFrontEnd();
13461     exit(status);
13462 }
13463
13464 void
13465 PauseEngine (ChessProgramState *cps)
13466 {
13467     SendToProgram("pause\n", cps);
13468     cps->pause = 2;
13469 }
13470
13471 void
13472 UnPauseEngine (ChessProgramState *cps)
13473 {
13474     SendToProgram("resume\n", cps);
13475     cps->pause = 1;
13476 }
13477
13478 void
13479 PauseEvent ()
13480 {
13481     if (appData.debugMode)
13482         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13483     if (pausing) {
13484         pausing = FALSE;
13485         ModeHighlight();
13486         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13487             StartClocks();
13488             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13489                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13490                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13491             }
13492             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13493             HandleMachineMove(stashedInputMove, stalledEngine);
13494             stalledEngine = NULL;
13495             return;
13496         }
13497         if (gameMode == MachinePlaysWhite ||
13498             gameMode == TwoMachinesPlay   ||
13499             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13500             if(first.pause)  UnPauseEngine(&first);
13501             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13502             if(second.pause) UnPauseEngine(&second);
13503             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13504             StartClocks();
13505         } else {
13506             DisplayBothClocks();
13507         }
13508         if (gameMode == PlayFromGameFile) {
13509             if (appData.timeDelay >= 0)
13510                 AutoPlayGameLoop();
13511         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13512             Reset(FALSE, TRUE);
13513             SendToICS(ics_prefix);
13514             SendToICS("refresh\n");
13515         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13516             ForwardInner(forwardMostMove);
13517         }
13518         pauseExamInvalid = FALSE;
13519     } else {
13520         switch (gameMode) {
13521           default:
13522             return;
13523           case IcsExamining:
13524             pauseExamForwardMostMove = forwardMostMove;
13525             pauseExamInvalid = FALSE;
13526             /* fall through */
13527           case IcsObserving:
13528           case IcsPlayingWhite:
13529           case IcsPlayingBlack:
13530             pausing = TRUE;
13531             ModeHighlight();
13532             return;
13533           case PlayFromGameFile:
13534             (void) StopLoadGameTimer();
13535             pausing = TRUE;
13536             ModeHighlight();
13537             break;
13538           case BeginningOfGame:
13539             if (appData.icsActive) return;
13540             /* else fall through */
13541           case MachinePlaysWhite:
13542           case MachinePlaysBlack:
13543           case TwoMachinesPlay:
13544             if (forwardMostMove == 0)
13545               return;           /* don't pause if no one has moved */
13546             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13547                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13548                 if(onMove->pause) {           // thinking engine can be paused
13549                     PauseEngine(onMove);      // do it
13550                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13551                         PauseEngine(onMove->other);
13552                     else
13553                         SendToProgram("easy\n", onMove->other);
13554                     StopClocks();
13555                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13556             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13557                 if(first.pause) {
13558                     PauseEngine(&first);
13559                     StopClocks();
13560                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13561             } else { // human on move, pause pondering by either method
13562                 if(first.pause)
13563                     PauseEngine(&first);
13564                 else if(appData.ponderNextMove)
13565                     SendToProgram("easy\n", &first);
13566                 StopClocks();
13567             }
13568             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13569           case AnalyzeMode:
13570             pausing = TRUE;
13571             ModeHighlight();
13572             break;
13573         }
13574     }
13575 }
13576
13577 void
13578 EditCommentEvent ()
13579 {
13580     char title[MSG_SIZ];
13581
13582     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13583       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13584     } else {
13585       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13586                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13587                parseList[currentMove - 1]);
13588     }
13589
13590     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13591 }
13592
13593
13594 void
13595 EditTagsEvent ()
13596 {
13597     char *tags = PGNTags(&gameInfo);
13598     bookUp = FALSE;
13599     EditTagsPopUp(tags, NULL);
13600     free(tags);
13601 }
13602
13603 void
13604 ToggleSecond ()
13605 {
13606   if(second.analyzing) {
13607     SendToProgram("exit\n", &second);
13608     second.analyzing = FALSE;
13609   } else {
13610     if (second.pr == NoProc) StartChessProgram(&second);
13611     InitChessProgram(&second, FALSE);
13612     FeedMovesToProgram(&second, currentMove);
13613
13614     SendToProgram("analyze\n", &second);
13615     second.analyzing = TRUE;
13616   }
13617 }
13618
13619 /* Toggle ShowThinking */
13620 void
13621 ToggleShowThinking()
13622 {
13623   appData.showThinking = !appData.showThinking;
13624   ShowThinkingEvent();
13625 }
13626
13627 int
13628 AnalyzeModeEvent ()
13629 {
13630     char buf[MSG_SIZ];
13631
13632     if (!first.analysisSupport) {
13633       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13634       DisplayError(buf, 0);
13635       return 0;
13636     }
13637     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13638     if (appData.icsActive) {
13639         if (gameMode != IcsObserving) {
13640           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13641             DisplayError(buf, 0);
13642             /* secure check */
13643             if (appData.icsEngineAnalyze) {
13644                 if (appData.debugMode)
13645                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13646                 ExitAnalyzeMode();
13647                 ModeHighlight();
13648             }
13649             return 0;
13650         }
13651         /* if enable, user wants to disable icsEngineAnalyze */
13652         if (appData.icsEngineAnalyze) {
13653                 ExitAnalyzeMode();
13654                 ModeHighlight();
13655                 return 0;
13656         }
13657         appData.icsEngineAnalyze = TRUE;
13658         if (appData.debugMode)
13659             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13660     }
13661
13662     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13663     if (appData.noChessProgram || gameMode == AnalyzeMode)
13664       return 0;
13665
13666     if (gameMode != AnalyzeFile) {
13667         if (!appData.icsEngineAnalyze) {
13668                EditGameEvent();
13669                if (gameMode != EditGame) return 0;
13670         }
13671         if (!appData.showThinking) ToggleShowThinking();
13672         ResurrectChessProgram();
13673         SendToProgram("analyze\n", &first);
13674         first.analyzing = TRUE;
13675         /*first.maybeThinking = TRUE;*/
13676         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13677         EngineOutputPopUp();
13678     }
13679     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13680     pausing = FALSE;
13681     ModeHighlight();
13682     SetGameInfo();
13683
13684     StartAnalysisClock();
13685     GetTimeMark(&lastNodeCountTime);
13686     lastNodeCount = 0;
13687     return 1;
13688 }
13689
13690 void
13691 AnalyzeFileEvent ()
13692 {
13693     if (appData.noChessProgram || gameMode == AnalyzeFile)
13694       return;
13695
13696     if (!first.analysisSupport) {
13697       char buf[MSG_SIZ];
13698       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13699       DisplayError(buf, 0);
13700       return;
13701     }
13702
13703     if (gameMode != AnalyzeMode) {
13704         keepInfo = 1; // mere annotating should not alter PGN tags
13705         EditGameEvent();
13706         keepInfo = 0;
13707         if (gameMode != EditGame) return;
13708         if (!appData.showThinking) ToggleShowThinking();
13709         ResurrectChessProgram();
13710         SendToProgram("analyze\n", &first);
13711         first.analyzing = TRUE;
13712         /*first.maybeThinking = TRUE;*/
13713         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13714         EngineOutputPopUp();
13715     }
13716     gameMode = AnalyzeFile;
13717     pausing = FALSE;
13718     ModeHighlight();
13719
13720     StartAnalysisClock();
13721     GetTimeMark(&lastNodeCountTime);
13722     lastNodeCount = 0;
13723     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13724     AnalysisPeriodicEvent(1);
13725 }
13726
13727 void
13728 MachineWhiteEvent ()
13729 {
13730     char buf[MSG_SIZ];
13731     char *bookHit = NULL;
13732
13733     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13734       return;
13735
13736
13737     if (gameMode == PlayFromGameFile ||
13738         gameMode == TwoMachinesPlay  ||
13739         gameMode == Training         ||
13740         gameMode == AnalyzeMode      ||
13741         gameMode == EndOfGame)
13742         EditGameEvent();
13743
13744     if (gameMode == EditPosition)
13745         EditPositionDone(TRUE);
13746
13747     if (!WhiteOnMove(currentMove)) {
13748         DisplayError(_("It is not White's turn"), 0);
13749         return;
13750     }
13751
13752     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13753       ExitAnalyzeMode();
13754
13755     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13756         gameMode == AnalyzeFile)
13757         TruncateGame();
13758
13759     ResurrectChessProgram();    /* in case it isn't running */
13760     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13761         gameMode = MachinePlaysWhite;
13762         ResetClocks();
13763     } else
13764     gameMode = MachinePlaysWhite;
13765     pausing = FALSE;
13766     ModeHighlight();
13767     SetGameInfo();
13768     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13769     DisplayTitle(buf);
13770     if (first.sendName) {
13771       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13772       SendToProgram(buf, &first);
13773     }
13774     if (first.sendTime) {
13775       if (first.useColors) {
13776         SendToProgram("black\n", &first); /*gnu kludge*/
13777       }
13778       SendTimeRemaining(&first, TRUE);
13779     }
13780     if (first.useColors) {
13781       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13782     }
13783     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13784     SetMachineThinkingEnables();
13785     first.maybeThinking = TRUE;
13786     StartClocks();
13787     firstMove = FALSE;
13788
13789     if (appData.autoFlipView && !flipView) {
13790       flipView = !flipView;
13791       DrawPosition(FALSE, NULL);
13792       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13793     }
13794
13795     if(bookHit) { // [HGM] book: simulate book reply
13796         static char bookMove[MSG_SIZ]; // a bit generous?
13797
13798         programStats.nodes = programStats.depth = programStats.time =
13799         programStats.score = programStats.got_only_move = 0;
13800         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13801
13802         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13803         strcat(bookMove, bookHit);
13804         HandleMachineMove(bookMove, &first);
13805     }
13806 }
13807
13808 void
13809 MachineBlackEvent ()
13810 {
13811   char buf[MSG_SIZ];
13812   char *bookHit = NULL;
13813
13814     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13815         return;
13816
13817
13818     if (gameMode == PlayFromGameFile ||
13819         gameMode == TwoMachinesPlay  ||
13820         gameMode == Training         ||
13821         gameMode == AnalyzeMode      ||
13822         gameMode == EndOfGame)
13823         EditGameEvent();
13824
13825     if (gameMode == EditPosition)
13826         EditPositionDone(TRUE);
13827
13828     if (WhiteOnMove(currentMove)) {
13829         DisplayError(_("It is not Black's turn"), 0);
13830         return;
13831     }
13832
13833     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13834       ExitAnalyzeMode();
13835
13836     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13837         gameMode == AnalyzeFile)
13838         TruncateGame();
13839
13840     ResurrectChessProgram();    /* in case it isn't running */
13841     gameMode = MachinePlaysBlack;
13842     pausing = FALSE;
13843     ModeHighlight();
13844     SetGameInfo();
13845     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13846     DisplayTitle(buf);
13847     if (first.sendName) {
13848       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13849       SendToProgram(buf, &first);
13850     }
13851     if (first.sendTime) {
13852       if (first.useColors) {
13853         SendToProgram("white\n", &first); /*gnu kludge*/
13854       }
13855       SendTimeRemaining(&first, FALSE);
13856     }
13857     if (first.useColors) {
13858       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13859     }
13860     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13861     SetMachineThinkingEnables();
13862     first.maybeThinking = TRUE;
13863     StartClocks();
13864
13865     if (appData.autoFlipView && flipView) {
13866       flipView = !flipView;
13867       DrawPosition(FALSE, NULL);
13868       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13869     }
13870     if(bookHit) { // [HGM] book: simulate book reply
13871         static char bookMove[MSG_SIZ]; // a bit generous?
13872
13873         programStats.nodes = programStats.depth = programStats.time =
13874         programStats.score = programStats.got_only_move = 0;
13875         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13876
13877         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13878         strcat(bookMove, bookHit);
13879         HandleMachineMove(bookMove, &first);
13880     }
13881 }
13882
13883
13884 void
13885 DisplayTwoMachinesTitle ()
13886 {
13887     char buf[MSG_SIZ];
13888     if (appData.matchGames > 0) {
13889         if(appData.tourneyFile[0]) {
13890           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13891                    gameInfo.white, _("vs."), gameInfo.black,
13892                    nextGame+1, appData.matchGames+1,
13893                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13894         } else
13895         if (first.twoMachinesColor[0] == 'w') {
13896           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13897                    gameInfo.white, _("vs."),  gameInfo.black,
13898                    first.matchWins, second.matchWins,
13899                    matchGame - 1 - (first.matchWins + second.matchWins));
13900         } else {
13901           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13902                    gameInfo.white, _("vs."), gameInfo.black,
13903                    second.matchWins, first.matchWins,
13904                    matchGame - 1 - (first.matchWins + second.matchWins));
13905         }
13906     } else {
13907       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13908     }
13909     DisplayTitle(buf);
13910 }
13911
13912 void
13913 SettingsMenuIfReady ()
13914 {
13915   if (second.lastPing != second.lastPong) {
13916     DisplayMessage("", _("Waiting for second chess program"));
13917     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13918     return;
13919   }
13920   ThawUI();
13921   DisplayMessage("", "");
13922   SettingsPopUp(&second);
13923 }
13924
13925 int
13926 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13927 {
13928     char buf[MSG_SIZ];
13929     if (cps->pr == NoProc) {
13930         StartChessProgram(cps);
13931         if (cps->protocolVersion == 1) {
13932           retry();
13933         } else {
13934           /* kludge: allow timeout for initial "feature" command */
13935           FreezeUI();
13936           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13937           DisplayMessage("", buf);
13938           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13939         }
13940         return 1;
13941     }
13942     return 0;
13943 }
13944
13945 void
13946 TwoMachinesEvent P((void))
13947 {
13948     int i;
13949     char buf[MSG_SIZ];
13950     ChessProgramState *onmove;
13951     char *bookHit = NULL;
13952     static int stalling = 0;
13953     TimeMark now;
13954     long wait;
13955
13956     if (appData.noChessProgram) return;
13957
13958     switch (gameMode) {
13959       case TwoMachinesPlay:
13960         return;
13961       case MachinePlaysWhite:
13962       case MachinePlaysBlack:
13963         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13964             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13965             return;
13966         }
13967         /* fall through */
13968       case BeginningOfGame:
13969       case PlayFromGameFile:
13970       case EndOfGame:
13971         EditGameEvent();
13972         if (gameMode != EditGame) return;
13973         break;
13974       case EditPosition:
13975         EditPositionDone(TRUE);
13976         break;
13977       case AnalyzeMode:
13978       case AnalyzeFile:
13979         ExitAnalyzeMode();
13980         break;
13981       case EditGame:
13982       default:
13983         break;
13984     }
13985
13986 //    forwardMostMove = currentMove;
13987     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13988
13989     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13990
13991     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13992     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13993       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13994       return;
13995     }
13996
13997     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13998         DisplayError("second engine does not play this", 0);
13999         return;
14000     }
14001
14002     if(!stalling) {
14003       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14004       SendToProgram("force\n", &second);
14005       stalling = 1;
14006       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14007       return;
14008     }
14009     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14010     if(appData.matchPause>10000 || appData.matchPause<10)
14011                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14012     wait = SubtractTimeMarks(&now, &pauseStart);
14013     if(wait < appData.matchPause) {
14014         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14015         return;
14016     }
14017     // we are now committed to starting the game
14018     stalling = 0;
14019     DisplayMessage("", "");
14020     if (startedFromSetupPosition) {
14021         SendBoard(&second, backwardMostMove);
14022     if (appData.debugMode) {
14023         fprintf(debugFP, "Two Machines\n");
14024     }
14025     }
14026     for (i = backwardMostMove; i < forwardMostMove; i++) {
14027         SendMoveToProgram(i, &second);
14028     }
14029
14030     gameMode = TwoMachinesPlay;
14031     pausing = FALSE;
14032     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14033     SetGameInfo();
14034     DisplayTwoMachinesTitle();
14035     firstMove = TRUE;
14036     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14037         onmove = &first;
14038     } else {
14039         onmove = &second;
14040     }
14041     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14042     SendToProgram(first.computerString, &first);
14043     if (first.sendName) {
14044       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14045       SendToProgram(buf, &first);
14046     }
14047     SendToProgram(second.computerString, &second);
14048     if (second.sendName) {
14049       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14050       SendToProgram(buf, &second);
14051     }
14052
14053     ResetClocks();
14054     if (!first.sendTime || !second.sendTime) {
14055         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14056         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14057     }
14058     if (onmove->sendTime) {
14059       if (onmove->useColors) {
14060         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14061       }
14062       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14063     }
14064     if (onmove->useColors) {
14065       SendToProgram(onmove->twoMachinesColor, onmove);
14066     }
14067     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14068 //    SendToProgram("go\n", onmove);
14069     onmove->maybeThinking = TRUE;
14070     SetMachineThinkingEnables();
14071
14072     StartClocks();
14073
14074     if(bookHit) { // [HGM] book: simulate book reply
14075         static char bookMove[MSG_SIZ]; // a bit generous?
14076
14077         programStats.nodes = programStats.depth = programStats.time =
14078         programStats.score = programStats.got_only_move = 0;
14079         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14080
14081         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14082         strcat(bookMove, bookHit);
14083         savedMessage = bookMove; // args for deferred call
14084         savedState = onmove;
14085         ScheduleDelayedEvent(DeferredBookMove, 1);
14086     }
14087 }
14088
14089 void
14090 TrainingEvent ()
14091 {
14092     if (gameMode == Training) {
14093       SetTrainingModeOff();
14094       gameMode = PlayFromGameFile;
14095       DisplayMessage("", _("Training mode off"));
14096     } else {
14097       gameMode = Training;
14098       animateTraining = appData.animate;
14099
14100       /* make sure we are not already at the end of the game */
14101       if (currentMove < forwardMostMove) {
14102         SetTrainingModeOn();
14103         DisplayMessage("", _("Training mode on"));
14104       } else {
14105         gameMode = PlayFromGameFile;
14106         DisplayError(_("Already at end of game"), 0);
14107       }
14108     }
14109     ModeHighlight();
14110 }
14111
14112 void
14113 IcsClientEvent ()
14114 {
14115     if (!appData.icsActive) return;
14116     switch (gameMode) {
14117       case IcsPlayingWhite:
14118       case IcsPlayingBlack:
14119       case IcsObserving:
14120       case IcsIdle:
14121       case BeginningOfGame:
14122       case IcsExamining:
14123         return;
14124
14125       case EditGame:
14126         break;
14127
14128       case EditPosition:
14129         EditPositionDone(TRUE);
14130         break;
14131
14132       case AnalyzeMode:
14133       case AnalyzeFile:
14134         ExitAnalyzeMode();
14135         break;
14136
14137       default:
14138         EditGameEvent();
14139         break;
14140     }
14141
14142     gameMode = IcsIdle;
14143     ModeHighlight();
14144     return;
14145 }
14146
14147 void
14148 EditGameEvent ()
14149 {
14150     int i;
14151
14152     switch (gameMode) {
14153       case Training:
14154         SetTrainingModeOff();
14155         break;
14156       case MachinePlaysWhite:
14157       case MachinePlaysBlack:
14158       case BeginningOfGame:
14159         SendToProgram("force\n", &first);
14160         SetUserThinkingEnables();
14161         break;
14162       case PlayFromGameFile:
14163         (void) StopLoadGameTimer();
14164         if (gameFileFP != NULL) {
14165             gameFileFP = NULL;
14166         }
14167         break;
14168       case EditPosition:
14169         EditPositionDone(TRUE);
14170         break;
14171       case AnalyzeMode:
14172       case AnalyzeFile:
14173         ExitAnalyzeMode();
14174         SendToProgram("force\n", &first);
14175         break;
14176       case TwoMachinesPlay:
14177         GameEnds(EndOfFile, NULL, GE_PLAYER);
14178         ResurrectChessProgram();
14179         SetUserThinkingEnables();
14180         break;
14181       case EndOfGame:
14182         ResurrectChessProgram();
14183         break;
14184       case IcsPlayingBlack:
14185       case IcsPlayingWhite:
14186         DisplayError(_("Warning: You are still playing a game"), 0);
14187         break;
14188       case IcsObserving:
14189         DisplayError(_("Warning: You are still observing a game"), 0);
14190         break;
14191       case IcsExamining:
14192         DisplayError(_("Warning: You are still examining a game"), 0);
14193         break;
14194       case IcsIdle:
14195         break;
14196       case EditGame:
14197       default:
14198         return;
14199     }
14200
14201     pausing = FALSE;
14202     StopClocks();
14203     first.offeredDraw = second.offeredDraw = 0;
14204
14205     if (gameMode == PlayFromGameFile) {
14206         whiteTimeRemaining = timeRemaining[0][currentMove];
14207         blackTimeRemaining = timeRemaining[1][currentMove];
14208         DisplayTitle("");
14209     }
14210
14211     if (gameMode == MachinePlaysWhite ||
14212         gameMode == MachinePlaysBlack ||
14213         gameMode == TwoMachinesPlay ||
14214         gameMode == EndOfGame) {
14215         i = forwardMostMove;
14216         while (i > currentMove) {
14217             SendToProgram("undo\n", &first);
14218             i--;
14219         }
14220         if(!adjustedClock) {
14221         whiteTimeRemaining = timeRemaining[0][currentMove];
14222         blackTimeRemaining = timeRemaining[1][currentMove];
14223         DisplayBothClocks();
14224         }
14225         if (whiteFlag || blackFlag) {
14226             whiteFlag = blackFlag = 0;
14227         }
14228         DisplayTitle("");
14229     }
14230
14231     gameMode = EditGame;
14232     ModeHighlight();
14233     SetGameInfo();
14234 }
14235
14236
14237 void
14238 EditPositionEvent ()
14239 {
14240     if (gameMode == EditPosition) {
14241         EditGameEvent();
14242         return;
14243     }
14244
14245     EditGameEvent();
14246     if (gameMode != EditGame) return;
14247
14248     gameMode = EditPosition;
14249     ModeHighlight();
14250     SetGameInfo();
14251     if (currentMove > 0)
14252       CopyBoard(boards[0], boards[currentMove]);
14253
14254     blackPlaysFirst = !WhiteOnMove(currentMove);
14255     ResetClocks();
14256     currentMove = forwardMostMove = backwardMostMove = 0;
14257     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14258     DisplayMove(-1);
14259     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14260 }
14261
14262 void
14263 ExitAnalyzeMode ()
14264 {
14265     /* [DM] icsEngineAnalyze - possible call from other functions */
14266     if (appData.icsEngineAnalyze) {
14267         appData.icsEngineAnalyze = FALSE;
14268
14269         DisplayMessage("",_("Close ICS engine analyze..."));
14270     }
14271     if (first.analysisSupport && first.analyzing) {
14272       SendToBoth("exit\n");
14273       first.analyzing = second.analyzing = FALSE;
14274     }
14275     thinkOutput[0] = NULLCHAR;
14276 }
14277
14278 void
14279 EditPositionDone (Boolean fakeRights)
14280 {
14281     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14282
14283     startedFromSetupPosition = TRUE;
14284     InitChessProgram(&first, FALSE);
14285     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14286       boards[0][EP_STATUS] = EP_NONE;
14287       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14288       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14289         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14290         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14291       } else boards[0][CASTLING][2] = NoRights;
14292       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14293         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14294         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14295       } else boards[0][CASTLING][5] = NoRights;
14296       if(gameInfo.variant == VariantSChess) {
14297         int i;
14298         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14299           boards[0][VIRGIN][i] = 0;
14300           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14301           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14302         }
14303       }
14304     }
14305     SendToProgram("force\n", &first);
14306     if (blackPlaysFirst) {
14307         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14308         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14309         currentMove = forwardMostMove = backwardMostMove = 1;
14310         CopyBoard(boards[1], boards[0]);
14311     } else {
14312         currentMove = forwardMostMove = backwardMostMove = 0;
14313     }
14314     SendBoard(&first, forwardMostMove);
14315     if (appData.debugMode) {
14316         fprintf(debugFP, "EditPosDone\n");
14317     }
14318     DisplayTitle("");
14319     DisplayMessage("", "");
14320     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14321     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14322     gameMode = EditGame;
14323     ModeHighlight();
14324     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14325     ClearHighlights(); /* [AS] */
14326 }
14327
14328 /* Pause for `ms' milliseconds */
14329 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14330 void
14331 TimeDelay (long ms)
14332 {
14333     TimeMark m1, m2;
14334
14335     GetTimeMark(&m1);
14336     do {
14337         GetTimeMark(&m2);
14338     } while (SubtractTimeMarks(&m2, &m1) < ms);
14339 }
14340
14341 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14342 void
14343 SendMultiLineToICS (char *buf)
14344 {
14345     char temp[MSG_SIZ+1], *p;
14346     int len;
14347
14348     len = strlen(buf);
14349     if (len > MSG_SIZ)
14350       len = MSG_SIZ;
14351
14352     strncpy(temp, buf, len);
14353     temp[len] = 0;
14354
14355     p = temp;
14356     while (*p) {
14357         if (*p == '\n' || *p == '\r')
14358           *p = ' ';
14359         ++p;
14360     }
14361
14362     strcat(temp, "\n");
14363     SendToICS(temp);
14364     SendToPlayer(temp, strlen(temp));
14365 }
14366
14367 void
14368 SetWhiteToPlayEvent ()
14369 {
14370     if (gameMode == EditPosition) {
14371         blackPlaysFirst = FALSE;
14372         DisplayBothClocks();    /* works because currentMove is 0 */
14373     } else if (gameMode == IcsExamining) {
14374         SendToICS(ics_prefix);
14375         SendToICS("tomove white\n");
14376     }
14377 }
14378
14379 void
14380 SetBlackToPlayEvent ()
14381 {
14382     if (gameMode == EditPosition) {
14383         blackPlaysFirst = TRUE;
14384         currentMove = 1;        /* kludge */
14385         DisplayBothClocks();
14386         currentMove = 0;
14387     } else if (gameMode == IcsExamining) {
14388         SendToICS(ics_prefix);
14389         SendToICS("tomove black\n");
14390     }
14391 }
14392
14393 void
14394 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14395 {
14396     char buf[MSG_SIZ];
14397     ChessSquare piece = boards[0][y][x];
14398
14399     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14400
14401     switch (selection) {
14402       case ClearBoard:
14403         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14404             SendToICS(ics_prefix);
14405             SendToICS("bsetup clear\n");
14406         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14407             SendToICS(ics_prefix);
14408             SendToICS("clearboard\n");
14409         } else {
14410             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14411                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14412                 for (y = 0; y < BOARD_HEIGHT; y++) {
14413                     if (gameMode == IcsExamining) {
14414                         if (boards[currentMove][y][x] != EmptySquare) {
14415                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14416                                     AAA + x, ONE + y);
14417                             SendToICS(buf);
14418                         }
14419                     } else {
14420                         boards[0][y][x] = p;
14421                     }
14422                 }
14423             }
14424         }
14425         if (gameMode == EditPosition) {
14426             DrawPosition(FALSE, boards[0]);
14427         }
14428         break;
14429
14430       case WhitePlay:
14431         SetWhiteToPlayEvent();
14432         break;
14433
14434       case BlackPlay:
14435         SetBlackToPlayEvent();
14436         break;
14437
14438       case EmptySquare:
14439         if (gameMode == IcsExamining) {
14440             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14441             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14442             SendToICS(buf);
14443         } else {
14444             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14445                 if(x == BOARD_LEFT-2) {
14446                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14447                     boards[0][y][1] = 0;
14448                 } else
14449                 if(x == BOARD_RGHT+1) {
14450                     if(y >= gameInfo.holdingsSize) break;
14451                     boards[0][y][BOARD_WIDTH-2] = 0;
14452                 } else break;
14453             }
14454             boards[0][y][x] = EmptySquare;
14455             DrawPosition(FALSE, boards[0]);
14456         }
14457         break;
14458
14459       case PromotePiece:
14460         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14461            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14462             selection = (ChessSquare) (PROMOTED piece);
14463         } else if(piece == EmptySquare) selection = WhiteSilver;
14464         else selection = (ChessSquare)((int)piece - 1);
14465         goto defaultlabel;
14466
14467       case DemotePiece:
14468         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14469            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14470             selection = (ChessSquare) (DEMOTED piece);
14471         } else if(piece == EmptySquare) selection = BlackSilver;
14472         else selection = (ChessSquare)((int)piece + 1);
14473         goto defaultlabel;
14474
14475       case WhiteQueen:
14476       case BlackQueen:
14477         if(gameInfo.variant == VariantShatranj ||
14478            gameInfo.variant == VariantXiangqi  ||
14479            gameInfo.variant == VariantCourier  ||
14480            gameInfo.variant == VariantMakruk     )
14481             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14482         goto defaultlabel;
14483
14484       case WhiteKing:
14485       case BlackKing:
14486         if(gameInfo.variant == VariantXiangqi)
14487             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14488         if(gameInfo.variant == VariantKnightmate)
14489             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14490       default:
14491         defaultlabel:
14492         if (gameMode == IcsExamining) {
14493             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14494             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14495                      PieceToChar(selection), AAA + x, ONE + y);
14496             SendToICS(buf);
14497         } else {
14498             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14499                 int n;
14500                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14501                     n = PieceToNumber(selection - BlackPawn);
14502                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14503                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14504                     boards[0][BOARD_HEIGHT-1-n][1]++;
14505                 } else
14506                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14507                     n = PieceToNumber(selection);
14508                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14509                     boards[0][n][BOARD_WIDTH-1] = selection;
14510                     boards[0][n][BOARD_WIDTH-2]++;
14511                 }
14512             } else
14513             boards[0][y][x] = selection;
14514             DrawPosition(TRUE, boards[0]);
14515             ClearHighlights();
14516             fromX = fromY = -1;
14517         }
14518         break;
14519     }
14520 }
14521
14522
14523 void
14524 DropMenuEvent (ChessSquare selection, int x, int y)
14525 {
14526     ChessMove moveType;
14527
14528     switch (gameMode) {
14529       case IcsPlayingWhite:
14530       case MachinePlaysBlack:
14531         if (!WhiteOnMove(currentMove)) {
14532             DisplayMoveError(_("It is Black's turn"));
14533             return;
14534         }
14535         moveType = WhiteDrop;
14536         break;
14537       case IcsPlayingBlack:
14538       case MachinePlaysWhite:
14539         if (WhiteOnMove(currentMove)) {
14540             DisplayMoveError(_("It is White's turn"));
14541             return;
14542         }
14543         moveType = BlackDrop;
14544         break;
14545       case EditGame:
14546         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14547         break;
14548       default:
14549         return;
14550     }
14551
14552     if (moveType == BlackDrop && selection < BlackPawn) {
14553       selection = (ChessSquare) ((int) selection
14554                                  + (int) BlackPawn - (int) WhitePawn);
14555     }
14556     if (boards[currentMove][y][x] != EmptySquare) {
14557         DisplayMoveError(_("That square is occupied"));
14558         return;
14559     }
14560
14561     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14562 }
14563
14564 void
14565 AcceptEvent ()
14566 {
14567     /* Accept a pending offer of any kind from opponent */
14568
14569     if (appData.icsActive) {
14570         SendToICS(ics_prefix);
14571         SendToICS("accept\n");
14572     } else if (cmailMsgLoaded) {
14573         if (currentMove == cmailOldMove &&
14574             commentList[cmailOldMove] != NULL &&
14575             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14576                    "Black offers a draw" : "White offers a draw")) {
14577             TruncateGame();
14578             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14579             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14580         } else {
14581             DisplayError(_("There is no pending offer on this move"), 0);
14582             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14583         }
14584     } else {
14585         /* Not used for offers from chess program */
14586     }
14587 }
14588
14589 void
14590 DeclineEvent ()
14591 {
14592     /* Decline a pending offer of any kind from opponent */
14593
14594     if (appData.icsActive) {
14595         SendToICS(ics_prefix);
14596         SendToICS("decline\n");
14597     } else if (cmailMsgLoaded) {
14598         if (currentMove == cmailOldMove &&
14599             commentList[cmailOldMove] != NULL &&
14600             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14601                    "Black offers a draw" : "White offers a draw")) {
14602 #ifdef NOTDEF
14603             AppendComment(cmailOldMove, "Draw declined", TRUE);
14604             DisplayComment(cmailOldMove - 1, "Draw declined");
14605 #endif /*NOTDEF*/
14606         } else {
14607             DisplayError(_("There is no pending offer on this move"), 0);
14608         }
14609     } else {
14610         /* Not used for offers from chess program */
14611     }
14612 }
14613
14614 void
14615 RematchEvent ()
14616 {
14617     /* Issue ICS rematch command */
14618     if (appData.icsActive) {
14619         SendToICS(ics_prefix);
14620         SendToICS("rematch\n");
14621     }
14622 }
14623
14624 void
14625 CallFlagEvent ()
14626 {
14627     /* Call your opponent's flag (claim a win on time) */
14628     if (appData.icsActive) {
14629         SendToICS(ics_prefix);
14630         SendToICS("flag\n");
14631     } else {
14632         switch (gameMode) {
14633           default:
14634             return;
14635           case MachinePlaysWhite:
14636             if (whiteFlag) {
14637                 if (blackFlag)
14638                   GameEnds(GameIsDrawn, "Both players ran out of time",
14639                            GE_PLAYER);
14640                 else
14641                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14642             } else {
14643                 DisplayError(_("Your opponent is not out of time"), 0);
14644             }
14645             break;
14646           case MachinePlaysBlack:
14647             if (blackFlag) {
14648                 if (whiteFlag)
14649                   GameEnds(GameIsDrawn, "Both players ran out of time",
14650                            GE_PLAYER);
14651                 else
14652                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14653             } else {
14654                 DisplayError(_("Your opponent is not out of time"), 0);
14655             }
14656             break;
14657         }
14658     }
14659 }
14660
14661 void
14662 ClockClick (int which)
14663 {       // [HGM] code moved to back-end from winboard.c
14664         if(which) { // black clock
14665           if (gameMode == EditPosition || gameMode == IcsExamining) {
14666             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14667             SetBlackToPlayEvent();
14668           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14669           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14670           } else if (shiftKey) {
14671             AdjustClock(which, -1);
14672           } else if (gameMode == IcsPlayingWhite ||
14673                      gameMode == MachinePlaysBlack) {
14674             CallFlagEvent();
14675           }
14676         } else { // white clock
14677           if (gameMode == EditPosition || gameMode == IcsExamining) {
14678             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14679             SetWhiteToPlayEvent();
14680           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14681           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14682           } else if (shiftKey) {
14683             AdjustClock(which, -1);
14684           } else if (gameMode == IcsPlayingBlack ||
14685                    gameMode == MachinePlaysWhite) {
14686             CallFlagEvent();
14687           }
14688         }
14689 }
14690
14691 void
14692 DrawEvent ()
14693 {
14694     /* Offer draw or accept pending draw offer from opponent */
14695
14696     if (appData.icsActive) {
14697         /* Note: tournament rules require draw offers to be
14698            made after you make your move but before you punch
14699            your clock.  Currently ICS doesn't let you do that;
14700            instead, you immediately punch your clock after making
14701            a move, but you can offer a draw at any time. */
14702
14703         SendToICS(ics_prefix);
14704         SendToICS("draw\n");
14705         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14706     } else if (cmailMsgLoaded) {
14707         if (currentMove == cmailOldMove &&
14708             commentList[cmailOldMove] != NULL &&
14709             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14710                    "Black offers a draw" : "White offers a draw")) {
14711             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14712             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14713         } else if (currentMove == cmailOldMove + 1) {
14714             char *offer = WhiteOnMove(cmailOldMove) ?
14715               "White offers a draw" : "Black offers a draw";
14716             AppendComment(currentMove, offer, TRUE);
14717             DisplayComment(currentMove - 1, offer);
14718             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14719         } else {
14720             DisplayError(_("You must make your move before offering a draw"), 0);
14721             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14722         }
14723     } else if (first.offeredDraw) {
14724         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14725     } else {
14726         if (first.sendDrawOffers) {
14727             SendToProgram("draw\n", &first);
14728             userOfferedDraw = TRUE;
14729         }
14730     }
14731 }
14732
14733 void
14734 AdjournEvent ()
14735 {
14736     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14737
14738     if (appData.icsActive) {
14739         SendToICS(ics_prefix);
14740         SendToICS("adjourn\n");
14741     } else {
14742         /* Currently GNU Chess doesn't offer or accept Adjourns */
14743     }
14744 }
14745
14746
14747 void
14748 AbortEvent ()
14749 {
14750     /* Offer Abort or accept pending Abort offer from opponent */
14751
14752     if (appData.icsActive) {
14753         SendToICS(ics_prefix);
14754         SendToICS("abort\n");
14755     } else {
14756         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14757     }
14758 }
14759
14760 void
14761 ResignEvent ()
14762 {
14763     /* Resign.  You can do this even if it's not your turn. */
14764
14765     if (appData.icsActive) {
14766         SendToICS(ics_prefix);
14767         SendToICS("resign\n");
14768     } else {
14769         switch (gameMode) {
14770           case MachinePlaysWhite:
14771             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14772             break;
14773           case MachinePlaysBlack:
14774             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14775             break;
14776           case EditGame:
14777             if (cmailMsgLoaded) {
14778                 TruncateGame();
14779                 if (WhiteOnMove(cmailOldMove)) {
14780                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14781                 } else {
14782                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14783                 }
14784                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14785             }
14786             break;
14787           default:
14788             break;
14789         }
14790     }
14791 }
14792
14793
14794 void
14795 StopObservingEvent ()
14796 {
14797     /* Stop observing current games */
14798     SendToICS(ics_prefix);
14799     SendToICS("unobserve\n");
14800 }
14801
14802 void
14803 StopExaminingEvent ()
14804 {
14805     /* Stop observing current game */
14806     SendToICS(ics_prefix);
14807     SendToICS("unexamine\n");
14808 }
14809
14810 void
14811 ForwardInner (int target)
14812 {
14813     int limit; int oldSeekGraphUp = seekGraphUp;
14814
14815     if (appData.debugMode)
14816         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14817                 target, currentMove, forwardMostMove);
14818
14819     if (gameMode == EditPosition)
14820       return;
14821
14822     seekGraphUp = FALSE;
14823     MarkTargetSquares(1);
14824
14825     if (gameMode == PlayFromGameFile && !pausing)
14826       PauseEvent();
14827
14828     if (gameMode == IcsExamining && pausing)
14829       limit = pauseExamForwardMostMove;
14830     else
14831       limit = forwardMostMove;
14832
14833     if (target > limit) target = limit;
14834
14835     if (target > 0 && moveList[target - 1][0]) {
14836         int fromX, fromY, toX, toY;
14837         toX = moveList[target - 1][2] - AAA;
14838         toY = moveList[target - 1][3] - ONE;
14839         if (moveList[target - 1][1] == '@') {
14840             if (appData.highlightLastMove) {
14841                 SetHighlights(-1, -1, toX, toY);
14842             }
14843         } else {
14844             fromX = moveList[target - 1][0] - AAA;
14845             fromY = moveList[target - 1][1] - ONE;
14846             if (target == currentMove + 1) {
14847                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14848             }
14849             if (appData.highlightLastMove) {
14850                 SetHighlights(fromX, fromY, toX, toY);
14851             }
14852         }
14853     }
14854     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14855         gameMode == Training || gameMode == PlayFromGameFile ||
14856         gameMode == AnalyzeFile) {
14857         while (currentMove < target) {
14858             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14859             SendMoveToProgram(currentMove++, &first);
14860         }
14861     } else {
14862         currentMove = target;
14863     }
14864
14865     if (gameMode == EditGame || gameMode == EndOfGame) {
14866         whiteTimeRemaining = timeRemaining[0][currentMove];
14867         blackTimeRemaining = timeRemaining[1][currentMove];
14868     }
14869     DisplayBothClocks();
14870     DisplayMove(currentMove - 1);
14871     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14872     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14873     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14874         DisplayComment(currentMove - 1, commentList[currentMove]);
14875     }
14876     ClearMap(); // [HGM] exclude: invalidate map
14877 }
14878
14879
14880 void
14881 ForwardEvent ()
14882 {
14883     if (gameMode == IcsExamining && !pausing) {
14884         SendToICS(ics_prefix);
14885         SendToICS("forward\n");
14886     } else {
14887         ForwardInner(currentMove + 1);
14888     }
14889 }
14890
14891 void
14892 ToEndEvent ()
14893 {
14894     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14895         /* to optimze, we temporarily turn off analysis mode while we feed
14896          * the remaining moves to the engine. Otherwise we get analysis output
14897          * after each move.
14898          */
14899         if (first.analysisSupport) {
14900           SendToProgram("exit\nforce\n", &first);
14901           first.analyzing = FALSE;
14902         }
14903     }
14904
14905     if (gameMode == IcsExamining && !pausing) {
14906         SendToICS(ics_prefix);
14907         SendToICS("forward 999999\n");
14908     } else {
14909         ForwardInner(forwardMostMove);
14910     }
14911
14912     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14913         /* we have fed all the moves, so reactivate analysis mode */
14914         SendToProgram("analyze\n", &first);
14915         first.analyzing = TRUE;
14916         /*first.maybeThinking = TRUE;*/
14917         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14918     }
14919 }
14920
14921 void
14922 BackwardInner (int target)
14923 {
14924     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14925
14926     if (appData.debugMode)
14927         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14928                 target, currentMove, forwardMostMove);
14929
14930     if (gameMode == EditPosition) return;
14931     seekGraphUp = FALSE;
14932     MarkTargetSquares(1);
14933     if (currentMove <= backwardMostMove) {
14934         ClearHighlights();
14935         DrawPosition(full_redraw, boards[currentMove]);
14936         return;
14937     }
14938     if (gameMode == PlayFromGameFile && !pausing)
14939       PauseEvent();
14940
14941     if (moveList[target][0]) {
14942         int fromX, fromY, toX, toY;
14943         toX = moveList[target][2] - AAA;
14944         toY = moveList[target][3] - ONE;
14945         if (moveList[target][1] == '@') {
14946             if (appData.highlightLastMove) {
14947                 SetHighlights(-1, -1, toX, toY);
14948             }
14949         } else {
14950             fromX = moveList[target][0] - AAA;
14951             fromY = moveList[target][1] - ONE;
14952             if (target == currentMove - 1) {
14953                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14954             }
14955             if (appData.highlightLastMove) {
14956                 SetHighlights(fromX, fromY, toX, toY);
14957             }
14958         }
14959     }
14960     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14961         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14962         while (currentMove > target) {
14963             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14964                 // null move cannot be undone. Reload program with move history before it.
14965                 int i;
14966                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14967                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14968                 }
14969                 SendBoard(&first, i);
14970               if(second.analyzing) SendBoard(&second, i);
14971                 for(currentMove=i; currentMove<target; currentMove++) {
14972                     SendMoveToProgram(currentMove, &first);
14973                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14974                 }
14975                 break;
14976             }
14977             SendToBoth("undo\n");
14978             currentMove--;
14979         }
14980     } else {
14981         currentMove = target;
14982     }
14983
14984     if (gameMode == EditGame || gameMode == EndOfGame) {
14985         whiteTimeRemaining = timeRemaining[0][currentMove];
14986         blackTimeRemaining = timeRemaining[1][currentMove];
14987     }
14988     DisplayBothClocks();
14989     DisplayMove(currentMove - 1);
14990     DrawPosition(full_redraw, boards[currentMove]);
14991     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14992     // [HGM] PV info: routine tests if comment empty
14993     DisplayComment(currentMove - 1, commentList[currentMove]);
14994     ClearMap(); // [HGM] exclude: invalidate map
14995 }
14996
14997 void
14998 BackwardEvent ()
14999 {
15000     if (gameMode == IcsExamining && !pausing) {
15001         SendToICS(ics_prefix);
15002         SendToICS("backward\n");
15003     } else {
15004         BackwardInner(currentMove - 1);
15005     }
15006 }
15007
15008 void
15009 ToStartEvent ()
15010 {
15011     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15012         /* to optimize, we temporarily turn off analysis mode while we undo
15013          * all the moves. Otherwise we get analysis output after each undo.
15014          */
15015         if (first.analysisSupport) {
15016           SendToProgram("exit\nforce\n", &first);
15017           first.analyzing = FALSE;
15018         }
15019     }
15020
15021     if (gameMode == IcsExamining && !pausing) {
15022         SendToICS(ics_prefix);
15023         SendToICS("backward 999999\n");
15024     } else {
15025         BackwardInner(backwardMostMove);
15026     }
15027
15028     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15029         /* we have fed all the moves, so reactivate analysis mode */
15030         SendToProgram("analyze\n", &first);
15031         first.analyzing = TRUE;
15032         /*first.maybeThinking = TRUE;*/
15033         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15034     }
15035 }
15036
15037 void
15038 ToNrEvent (int to)
15039 {
15040   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15041   if (to >= forwardMostMove) to = forwardMostMove;
15042   if (to <= backwardMostMove) to = backwardMostMove;
15043   if (to < currentMove) {
15044     BackwardInner(to);
15045   } else {
15046     ForwardInner(to);
15047   }
15048 }
15049
15050 void
15051 RevertEvent (Boolean annotate)
15052 {
15053     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15054         return;
15055     }
15056     if (gameMode != IcsExamining) {
15057         DisplayError(_("You are not examining a game"), 0);
15058         return;
15059     }
15060     if (pausing) {
15061         DisplayError(_("You can't revert while pausing"), 0);
15062         return;
15063     }
15064     SendToICS(ics_prefix);
15065     SendToICS("revert\n");
15066 }
15067
15068 void
15069 RetractMoveEvent ()
15070 {
15071     switch (gameMode) {
15072       case MachinePlaysWhite:
15073       case MachinePlaysBlack:
15074         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15075             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15076             return;
15077         }
15078         if (forwardMostMove < 2) return;
15079         currentMove = forwardMostMove = forwardMostMove - 2;
15080         whiteTimeRemaining = timeRemaining[0][currentMove];
15081         blackTimeRemaining = timeRemaining[1][currentMove];
15082         DisplayBothClocks();
15083         DisplayMove(currentMove - 1);
15084         ClearHighlights();/*!! could figure this out*/
15085         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15086         SendToProgram("remove\n", &first);
15087         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15088         break;
15089
15090       case BeginningOfGame:
15091       default:
15092         break;
15093
15094       case IcsPlayingWhite:
15095       case IcsPlayingBlack:
15096         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15097             SendToICS(ics_prefix);
15098             SendToICS("takeback 2\n");
15099         } else {
15100             SendToICS(ics_prefix);
15101             SendToICS("takeback 1\n");
15102         }
15103         break;
15104     }
15105 }
15106
15107 void
15108 MoveNowEvent ()
15109 {
15110     ChessProgramState *cps;
15111
15112     switch (gameMode) {
15113       case MachinePlaysWhite:
15114         if (!WhiteOnMove(forwardMostMove)) {
15115             DisplayError(_("It is your turn"), 0);
15116             return;
15117         }
15118         cps = &first;
15119         break;
15120       case MachinePlaysBlack:
15121         if (WhiteOnMove(forwardMostMove)) {
15122             DisplayError(_("It is your turn"), 0);
15123             return;
15124         }
15125         cps = &first;
15126         break;
15127       case TwoMachinesPlay:
15128         if (WhiteOnMove(forwardMostMove) ==
15129             (first.twoMachinesColor[0] == 'w')) {
15130             cps = &first;
15131         } else {
15132             cps = &second;
15133         }
15134         break;
15135       case BeginningOfGame:
15136       default:
15137         return;
15138     }
15139     SendToProgram("?\n", cps);
15140 }
15141
15142 void
15143 TruncateGameEvent ()
15144 {
15145     EditGameEvent();
15146     if (gameMode != EditGame) return;
15147     TruncateGame();
15148 }
15149
15150 void
15151 TruncateGame ()
15152 {
15153     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15154     if (forwardMostMove > currentMove) {
15155         if (gameInfo.resultDetails != NULL) {
15156             free(gameInfo.resultDetails);
15157             gameInfo.resultDetails = NULL;
15158             gameInfo.result = GameUnfinished;
15159         }
15160         forwardMostMove = currentMove;
15161         HistorySet(parseList, backwardMostMove, forwardMostMove,
15162                    currentMove-1);
15163     }
15164 }
15165
15166 void
15167 HintEvent ()
15168 {
15169     if (appData.noChessProgram) return;
15170     switch (gameMode) {
15171       case MachinePlaysWhite:
15172         if (WhiteOnMove(forwardMostMove)) {
15173             DisplayError(_("Wait until your turn"), 0);
15174             return;
15175         }
15176         break;
15177       case BeginningOfGame:
15178       case MachinePlaysBlack:
15179         if (!WhiteOnMove(forwardMostMove)) {
15180             DisplayError(_("Wait until your turn"), 0);
15181             return;
15182         }
15183         break;
15184       default:
15185         DisplayError(_("No hint available"), 0);
15186         return;
15187     }
15188     SendToProgram("hint\n", &first);
15189     hintRequested = TRUE;
15190 }
15191
15192 void
15193 CreateBookEvent ()
15194 {
15195     ListGame * lg = (ListGame *) gameList.head;
15196     FILE *f;
15197     int nItem;
15198     static int secondTime = FALSE;
15199
15200     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15201         DisplayError(_("Game list not loaded or empty"), 0);
15202         return;
15203     }
15204
15205     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15206         fclose(f);
15207         secondTime++;
15208         DisplayNote(_("Book file exists! Try again for overwrite."));
15209         return;
15210     }
15211
15212     creatingBook = TRUE;
15213     secondTime = FALSE;
15214
15215     /* Get list size */
15216     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15217         LoadGame(f, nItem, "", TRUE);
15218         AddGameToBook(TRUE);
15219         lg = (ListGame *) lg->node.succ;
15220     }
15221
15222     creatingBook = FALSE;
15223     FlushBook();
15224 }
15225
15226 void
15227 BookEvent ()
15228 {
15229     if (appData.noChessProgram) return;
15230     switch (gameMode) {
15231       case MachinePlaysWhite:
15232         if (WhiteOnMove(forwardMostMove)) {
15233             DisplayError(_("Wait until your turn"), 0);
15234             return;
15235         }
15236         break;
15237       case BeginningOfGame:
15238       case MachinePlaysBlack:
15239         if (!WhiteOnMove(forwardMostMove)) {
15240             DisplayError(_("Wait until your turn"), 0);
15241             return;
15242         }
15243         break;
15244       case EditPosition:
15245         EditPositionDone(TRUE);
15246         break;
15247       case TwoMachinesPlay:
15248         return;
15249       default:
15250         break;
15251     }
15252     SendToProgram("bk\n", &first);
15253     bookOutput[0] = NULLCHAR;
15254     bookRequested = TRUE;
15255 }
15256
15257 void
15258 AboutGameEvent ()
15259 {
15260     char *tags = PGNTags(&gameInfo);
15261     TagsPopUp(tags, CmailMsg());
15262     free(tags);
15263 }
15264
15265 /* end button procedures */
15266
15267 void
15268 PrintPosition (FILE *fp, int move)
15269 {
15270     int i, j;
15271
15272     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15273         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15274             char c = PieceToChar(boards[move][i][j]);
15275             fputc(c == 'x' ? '.' : c, fp);
15276             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15277         }
15278     }
15279     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15280       fprintf(fp, "white to play\n");
15281     else
15282       fprintf(fp, "black to play\n");
15283 }
15284
15285 void
15286 PrintOpponents (FILE *fp)
15287 {
15288     if (gameInfo.white != NULL) {
15289         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15290     } else {
15291         fprintf(fp, "\n");
15292     }
15293 }
15294
15295 /* Find last component of program's own name, using some heuristics */
15296 void
15297 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15298 {
15299     char *p, *q, c;
15300     int local = (strcmp(host, "localhost") == 0);
15301     while (!local && (p = strchr(prog, ';')) != NULL) {
15302         p++;
15303         while (*p == ' ') p++;
15304         prog = p;
15305     }
15306     if (*prog == '"' || *prog == '\'') {
15307         q = strchr(prog + 1, *prog);
15308     } else {
15309         q = strchr(prog, ' ');
15310     }
15311     if (q == NULL) q = prog + strlen(prog);
15312     p = q;
15313     while (p >= prog && *p != '/' && *p != '\\') p--;
15314     p++;
15315     if(p == prog && *p == '"') p++;
15316     c = *q; *q = 0;
15317     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15318     memcpy(buf, p, q - p);
15319     buf[q - p] = NULLCHAR;
15320     if (!local) {
15321         strcat(buf, "@");
15322         strcat(buf, host);
15323     }
15324 }
15325
15326 char *
15327 TimeControlTagValue ()
15328 {
15329     char buf[MSG_SIZ];
15330     if (!appData.clockMode) {
15331       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15332     } else if (movesPerSession > 0) {
15333       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15334     } else if (timeIncrement == 0) {
15335       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15336     } else {
15337       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15338     }
15339     return StrSave(buf);
15340 }
15341
15342 void
15343 SetGameInfo ()
15344 {
15345     /* This routine is used only for certain modes */
15346     VariantClass v = gameInfo.variant;
15347     ChessMove r = GameUnfinished;
15348     char *p = NULL;
15349
15350     if(keepInfo) return;
15351
15352     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15353         r = gameInfo.result;
15354         p = gameInfo.resultDetails;
15355         gameInfo.resultDetails = NULL;
15356     }
15357     ClearGameInfo(&gameInfo);
15358     gameInfo.variant = v;
15359
15360     switch (gameMode) {
15361       case MachinePlaysWhite:
15362         gameInfo.event = StrSave( appData.pgnEventHeader );
15363         gameInfo.site = StrSave(HostName());
15364         gameInfo.date = PGNDate();
15365         gameInfo.round = StrSave("-");
15366         gameInfo.white = StrSave(first.tidy);
15367         gameInfo.black = StrSave(UserName());
15368         gameInfo.timeControl = TimeControlTagValue();
15369         break;
15370
15371       case MachinePlaysBlack:
15372         gameInfo.event = StrSave( appData.pgnEventHeader );
15373         gameInfo.site = StrSave(HostName());
15374         gameInfo.date = PGNDate();
15375         gameInfo.round = StrSave("-");
15376         gameInfo.white = StrSave(UserName());
15377         gameInfo.black = StrSave(first.tidy);
15378         gameInfo.timeControl = TimeControlTagValue();
15379         break;
15380
15381       case TwoMachinesPlay:
15382         gameInfo.event = StrSave( appData.pgnEventHeader );
15383         gameInfo.site = StrSave(HostName());
15384         gameInfo.date = PGNDate();
15385         if (roundNr > 0) {
15386             char buf[MSG_SIZ];
15387             snprintf(buf, MSG_SIZ, "%d", roundNr);
15388             gameInfo.round = StrSave(buf);
15389         } else {
15390             gameInfo.round = StrSave("-");
15391         }
15392         if (first.twoMachinesColor[0] == 'w') {
15393             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15394             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15395         } else {
15396             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15397             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15398         }
15399         gameInfo.timeControl = TimeControlTagValue();
15400         break;
15401
15402       case EditGame:
15403         gameInfo.event = StrSave("Edited game");
15404         gameInfo.site = StrSave(HostName());
15405         gameInfo.date = PGNDate();
15406         gameInfo.round = StrSave("-");
15407         gameInfo.white = StrSave("-");
15408         gameInfo.black = StrSave("-");
15409         gameInfo.result = r;
15410         gameInfo.resultDetails = p;
15411         break;
15412
15413       case EditPosition:
15414         gameInfo.event = StrSave("Edited position");
15415         gameInfo.site = StrSave(HostName());
15416         gameInfo.date = PGNDate();
15417         gameInfo.round = StrSave("-");
15418         gameInfo.white = StrSave("-");
15419         gameInfo.black = StrSave("-");
15420         break;
15421
15422       case IcsPlayingWhite:
15423       case IcsPlayingBlack:
15424       case IcsObserving:
15425       case IcsExamining:
15426         break;
15427
15428       case PlayFromGameFile:
15429         gameInfo.event = StrSave("Game from non-PGN file");
15430         gameInfo.site = StrSave(HostName());
15431         gameInfo.date = PGNDate();
15432         gameInfo.round = StrSave("-");
15433         gameInfo.white = StrSave("?");
15434         gameInfo.black = StrSave("?");
15435         break;
15436
15437       default:
15438         break;
15439     }
15440 }
15441
15442 void
15443 ReplaceComment (int index, char *text)
15444 {
15445     int len;
15446     char *p;
15447     float score;
15448
15449     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15450        pvInfoList[index-1].depth == len &&
15451        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15452        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15453     while (*text == '\n') text++;
15454     len = strlen(text);
15455     while (len > 0 && text[len - 1] == '\n') len--;
15456
15457     if (commentList[index] != NULL)
15458       free(commentList[index]);
15459
15460     if (len == 0) {
15461         commentList[index] = NULL;
15462         return;
15463     }
15464   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15465       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15466       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15467     commentList[index] = (char *) malloc(len + 2);
15468     strncpy(commentList[index], text, len);
15469     commentList[index][len] = '\n';
15470     commentList[index][len + 1] = NULLCHAR;
15471   } else {
15472     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15473     char *p;
15474     commentList[index] = (char *) malloc(len + 7);
15475     safeStrCpy(commentList[index], "{\n", 3);
15476     safeStrCpy(commentList[index]+2, text, len+1);
15477     commentList[index][len+2] = NULLCHAR;
15478     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15479     strcat(commentList[index], "\n}\n");
15480   }
15481 }
15482
15483 void
15484 CrushCRs (char *text)
15485 {
15486   char *p = text;
15487   char *q = text;
15488   char ch;
15489
15490   do {
15491     ch = *p++;
15492     if (ch == '\r') continue;
15493     *q++ = ch;
15494   } while (ch != '\0');
15495 }
15496
15497 void
15498 AppendComment (int index, char *text, Boolean addBraces)
15499 /* addBraces  tells if we should add {} */
15500 {
15501     int oldlen, len;
15502     char *old;
15503
15504 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15505     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15506
15507     CrushCRs(text);
15508     while (*text == '\n') text++;
15509     len = strlen(text);
15510     while (len > 0 && text[len - 1] == '\n') len--;
15511     text[len] = NULLCHAR;
15512
15513     if (len == 0) return;
15514
15515     if (commentList[index] != NULL) {
15516       Boolean addClosingBrace = addBraces;
15517         old = commentList[index];
15518         oldlen = strlen(old);
15519         while(commentList[index][oldlen-1] ==  '\n')
15520           commentList[index][--oldlen] = NULLCHAR;
15521         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15522         safeStrCpy(commentList[index], old, oldlen + len + 6);
15523         free(old);
15524         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15525         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15526           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15527           while (*text == '\n') { text++; len--; }
15528           commentList[index][--oldlen] = NULLCHAR;
15529       }
15530         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15531         else          strcat(commentList[index], "\n");
15532         strcat(commentList[index], text);
15533         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15534         else          strcat(commentList[index], "\n");
15535     } else {
15536         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15537         if(addBraces)
15538           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15539         else commentList[index][0] = NULLCHAR;
15540         strcat(commentList[index], text);
15541         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15542         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15543     }
15544 }
15545
15546 static char *
15547 FindStr (char * text, char * sub_text)
15548 {
15549     char * result = strstr( text, sub_text );
15550
15551     if( result != NULL ) {
15552         result += strlen( sub_text );
15553     }
15554
15555     return result;
15556 }
15557
15558 /* [AS] Try to extract PV info from PGN comment */
15559 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15560 char *
15561 GetInfoFromComment (int index, char * text)
15562 {
15563     char * sep = text, *p;
15564
15565     if( text != NULL && index > 0 ) {
15566         int score = 0;
15567         int depth = 0;
15568         int time = -1, sec = 0, deci;
15569         char * s_eval = FindStr( text, "[%eval " );
15570         char * s_emt = FindStr( text, "[%emt " );
15571
15572         if( s_eval != NULL || s_emt != NULL ) {
15573             /* New style */
15574             char delim;
15575
15576             if( s_eval != NULL ) {
15577                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15578                     return text;
15579                 }
15580
15581                 if( delim != ']' ) {
15582                     return text;
15583                 }
15584             }
15585
15586             if( s_emt != NULL ) {
15587             }
15588                 return text;
15589         }
15590         else {
15591             /* We expect something like: [+|-]nnn.nn/dd */
15592             int score_lo = 0;
15593
15594             if(*text != '{') return text; // [HGM] braces: must be normal comment
15595
15596             sep = strchr( text, '/' );
15597             if( sep == NULL || sep < (text+4) ) {
15598                 return text;
15599             }
15600
15601             p = text;
15602             if(p[1] == '(') { // comment starts with PV
15603                p = strchr(p, ')'); // locate end of PV
15604                if(p == NULL || sep < p+5) return text;
15605                // at this point we have something like "{(.*) +0.23/6 ..."
15606                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15607                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15608                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15609             }
15610             time = -1; sec = -1; deci = -1;
15611             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15612                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15613                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15614                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15615                 return text;
15616             }
15617
15618             if( score_lo < 0 || score_lo >= 100 ) {
15619                 return text;
15620             }
15621
15622             if(sec >= 0) time = 600*time + 10*sec; else
15623             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15624
15625             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15626
15627             /* [HGM] PV time: now locate end of PV info */
15628             while( *++sep >= '0' && *sep <= '9'); // strip depth
15629             if(time >= 0)
15630             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15631             if(sec >= 0)
15632             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15633             if(deci >= 0)
15634             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15635             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15636         }
15637
15638         if( depth <= 0 ) {
15639             return text;
15640         }
15641
15642         if( time < 0 ) {
15643             time = -1;
15644         }
15645
15646         pvInfoList[index-1].depth = depth;
15647         pvInfoList[index-1].score = score;
15648         pvInfoList[index-1].time  = 10*time; // centi-sec
15649         if(*sep == '}') *sep = 0; else *--sep = '{';
15650         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15651     }
15652     return sep;
15653 }
15654
15655 void
15656 SendToProgram (char *message, ChessProgramState *cps)
15657 {
15658     int count, outCount, error;
15659     char buf[MSG_SIZ];
15660
15661     if (cps->pr == NoProc) return;
15662     Attention(cps);
15663
15664     if (appData.debugMode) {
15665         TimeMark now;
15666         GetTimeMark(&now);
15667         fprintf(debugFP, "%ld >%-6s: %s",
15668                 SubtractTimeMarks(&now, &programStartTime),
15669                 cps->which, message);
15670         if(serverFP)
15671             fprintf(serverFP, "%ld >%-6s: %s",
15672                 SubtractTimeMarks(&now, &programStartTime),
15673                 cps->which, message), fflush(serverFP);
15674     }
15675
15676     count = strlen(message);
15677     outCount = OutputToProcess(cps->pr, message, count, &error);
15678     if (outCount < count && !exiting
15679                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15680       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15681       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15682         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15683             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15684                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15685                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15686                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15687             } else {
15688                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15689                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15690                 gameInfo.result = res;
15691             }
15692             gameInfo.resultDetails = StrSave(buf);
15693         }
15694         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15695         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15696     }
15697 }
15698
15699 void
15700 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15701 {
15702     char *end_str;
15703     char buf[MSG_SIZ];
15704     ChessProgramState *cps = (ChessProgramState *)closure;
15705
15706     if (isr != cps->isr) return; /* Killed intentionally */
15707     if (count <= 0) {
15708         if (count == 0) {
15709             RemoveInputSource(cps->isr);
15710             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15711                     _(cps->which), cps->program);
15712             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15713             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15714                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15715                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15716                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15717                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15718                 } else {
15719                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15720                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15721                     gameInfo.result = res;
15722                 }
15723                 gameInfo.resultDetails = StrSave(buf);
15724             }
15725             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15726             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15727         } else {
15728             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15729                     _(cps->which), cps->program);
15730             RemoveInputSource(cps->isr);
15731
15732             /* [AS] Program is misbehaving badly... kill it */
15733             if( count == -2 ) {
15734                 DestroyChildProcess( cps->pr, 9 );
15735                 cps->pr = NoProc;
15736             }
15737
15738             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15739         }
15740         return;
15741     }
15742
15743     if ((end_str = strchr(message, '\r')) != NULL)
15744       *end_str = NULLCHAR;
15745     if ((end_str = strchr(message, '\n')) != NULL)
15746       *end_str = NULLCHAR;
15747
15748     if (appData.debugMode) {
15749         TimeMark now; int print = 1;
15750         char *quote = ""; char c; int i;
15751
15752         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15753                 char start = message[0];
15754                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15755                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15756                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15757                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15758                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15759                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15760                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15761                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15762                    sscanf(message, "hint: %c", &c)!=1 &&
15763                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15764                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15765                     print = (appData.engineComments >= 2);
15766                 }
15767                 message[0] = start; // restore original message
15768         }
15769         if(print) {
15770                 GetTimeMark(&now);
15771                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15772                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15773                         quote,
15774                         message);
15775                 if(serverFP)
15776                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15777                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15778                         quote,
15779                         message), fflush(serverFP);
15780         }
15781     }
15782
15783     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15784     if (appData.icsEngineAnalyze) {
15785         if (strstr(message, "whisper") != NULL ||
15786              strstr(message, "kibitz") != NULL ||
15787             strstr(message, "tellics") != NULL) return;
15788     }
15789
15790     HandleMachineMove(message, cps);
15791 }
15792
15793
15794 void
15795 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15796 {
15797     char buf[MSG_SIZ];
15798     int seconds;
15799
15800     if( timeControl_2 > 0 ) {
15801         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15802             tc = timeControl_2;
15803         }
15804     }
15805     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15806     inc /= cps->timeOdds;
15807     st  /= cps->timeOdds;
15808
15809     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15810
15811     if (st > 0) {
15812       /* Set exact time per move, normally using st command */
15813       if (cps->stKludge) {
15814         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15815         seconds = st % 60;
15816         if (seconds == 0) {
15817           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15818         } else {
15819           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15820         }
15821       } else {
15822         snprintf(buf, MSG_SIZ, "st %d\n", st);
15823       }
15824     } else {
15825       /* Set conventional or incremental time control, using level command */
15826       if (seconds == 0) {
15827         /* Note old gnuchess bug -- minutes:seconds used to not work.
15828            Fixed in later versions, but still avoid :seconds
15829            when seconds is 0. */
15830         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15831       } else {
15832         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15833                  seconds, inc/1000.);
15834       }
15835     }
15836     SendToProgram(buf, cps);
15837
15838     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15839     /* Orthogonally, limit search to given depth */
15840     if (sd > 0) {
15841       if (cps->sdKludge) {
15842         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15843       } else {
15844         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15845       }
15846       SendToProgram(buf, cps);
15847     }
15848
15849     if(cps->nps >= 0) { /* [HGM] nps */
15850         if(cps->supportsNPS == FALSE)
15851           cps->nps = -1; // don't use if engine explicitly says not supported!
15852         else {
15853           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15854           SendToProgram(buf, cps);
15855         }
15856     }
15857 }
15858
15859 ChessProgramState *
15860 WhitePlayer ()
15861 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15862 {
15863     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15864        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15865         return &second;
15866     return &first;
15867 }
15868
15869 void
15870 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15871 {
15872     char message[MSG_SIZ];
15873     long time, otime;
15874
15875     /* Note: this routine must be called when the clocks are stopped
15876        or when they have *just* been set or switched; otherwise
15877        it will be off by the time since the current tick started.
15878     */
15879     if (machineWhite) {
15880         time = whiteTimeRemaining / 10;
15881         otime = blackTimeRemaining / 10;
15882     } else {
15883         time = blackTimeRemaining / 10;
15884         otime = whiteTimeRemaining / 10;
15885     }
15886     /* [HGM] translate opponent's time by time-odds factor */
15887     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15888
15889     if (time <= 0) time = 1;
15890     if (otime <= 0) otime = 1;
15891
15892     snprintf(message, MSG_SIZ, "time %ld\n", time);
15893     SendToProgram(message, cps);
15894
15895     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15896     SendToProgram(message, cps);
15897 }
15898
15899 int
15900 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15901 {
15902   char buf[MSG_SIZ];
15903   int len = strlen(name);
15904   int val;
15905
15906   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15907     (*p) += len + 1;
15908     sscanf(*p, "%d", &val);
15909     *loc = (val != 0);
15910     while (**p && **p != ' ')
15911       (*p)++;
15912     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15913     SendToProgram(buf, cps);
15914     return TRUE;
15915   }
15916   return FALSE;
15917 }
15918
15919 int
15920 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15921 {
15922   char buf[MSG_SIZ];
15923   int len = strlen(name);
15924   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15925     (*p) += len + 1;
15926     sscanf(*p, "%d", loc);
15927     while (**p && **p != ' ') (*p)++;
15928     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15929     SendToProgram(buf, cps);
15930     return TRUE;
15931   }
15932   return FALSE;
15933 }
15934
15935 int
15936 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15937 {
15938   char buf[MSG_SIZ];
15939   int len = strlen(name);
15940   if (strncmp((*p), name, len) == 0
15941       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15942     (*p) += len + 2;
15943     sscanf(*p, "%[^\"]", loc);
15944     while (**p && **p != '\"') (*p)++;
15945     if (**p == '\"') (*p)++;
15946     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15947     SendToProgram(buf, cps);
15948     return TRUE;
15949   }
15950   return FALSE;
15951 }
15952
15953 int
15954 ParseOption (Option *opt, ChessProgramState *cps)
15955 // [HGM] options: process the string that defines an engine option, and determine
15956 // name, type, default value, and allowed value range
15957 {
15958         char *p, *q, buf[MSG_SIZ];
15959         int n, min = (-1)<<31, max = 1<<31, def;
15960
15961         if(p = strstr(opt->name, " -spin ")) {
15962             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15963             if(max < min) max = min; // enforce consistency
15964             if(def < min) def = min;
15965             if(def > max) def = max;
15966             opt->value = def;
15967             opt->min = min;
15968             opt->max = max;
15969             opt->type = Spin;
15970         } else if((p = strstr(opt->name, " -slider "))) {
15971             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15972             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15973             if(max < min) max = min; // enforce consistency
15974             if(def < min) def = min;
15975             if(def > max) def = max;
15976             opt->value = def;
15977             opt->min = min;
15978             opt->max = max;
15979             opt->type = Spin; // Slider;
15980         } else if((p = strstr(opt->name, " -string "))) {
15981             opt->textValue = p+9;
15982             opt->type = TextBox;
15983         } else if((p = strstr(opt->name, " -file "))) {
15984             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15985             opt->textValue = p+7;
15986             opt->type = FileName; // FileName;
15987         } else if((p = strstr(opt->name, " -path "))) {
15988             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15989             opt->textValue = p+7;
15990             opt->type = PathName; // PathName;
15991         } else if(p = strstr(opt->name, " -check ")) {
15992             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15993             opt->value = (def != 0);
15994             opt->type = CheckBox;
15995         } else if(p = strstr(opt->name, " -combo ")) {
15996             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15997             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15998             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15999             opt->value = n = 0;
16000             while(q = StrStr(q, " /// ")) {
16001                 n++; *q = 0;    // count choices, and null-terminate each of them
16002                 q += 5;
16003                 if(*q == '*') { // remember default, which is marked with * prefix
16004                     q++;
16005                     opt->value = n;
16006                 }
16007                 cps->comboList[cps->comboCnt++] = q;
16008             }
16009             cps->comboList[cps->comboCnt++] = NULL;
16010             opt->max = n + 1;
16011             opt->type = ComboBox;
16012         } else if(p = strstr(opt->name, " -button")) {
16013             opt->type = Button;
16014         } else if(p = strstr(opt->name, " -save")) {
16015             opt->type = SaveButton;
16016         } else return FALSE;
16017         *p = 0; // terminate option name
16018         // now look if the command-line options define a setting for this engine option.
16019         if(cps->optionSettings && cps->optionSettings[0])
16020             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16021         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16022           snprintf(buf, MSG_SIZ, "option %s", p);
16023                 if(p = strstr(buf, ",")) *p = 0;
16024                 if(q = strchr(buf, '=')) switch(opt->type) {
16025                     case ComboBox:
16026                         for(n=0; n<opt->max; n++)
16027                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16028                         break;
16029                     case TextBox:
16030                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16031                         break;
16032                     case Spin:
16033                     case CheckBox:
16034                         opt->value = atoi(q+1);
16035                     default:
16036                         break;
16037                 }
16038                 strcat(buf, "\n");
16039                 SendToProgram(buf, cps);
16040         }
16041         return TRUE;
16042 }
16043
16044 void
16045 FeatureDone (ChessProgramState *cps, int val)
16046 {
16047   DelayedEventCallback cb = GetDelayedEvent();
16048   if ((cb == InitBackEnd3 && cps == &first) ||
16049       (cb == SettingsMenuIfReady && cps == &second) ||
16050       (cb == LoadEngine) ||
16051       (cb == TwoMachinesEventIfReady)) {
16052     CancelDelayedEvent();
16053     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16054   }
16055   cps->initDone = val;
16056   if(val) cps->reload = FALSE;
16057 }
16058
16059 /* Parse feature command from engine */
16060 void
16061 ParseFeatures (char *args, ChessProgramState *cps)
16062 {
16063   char *p = args;
16064   char *q;
16065   int val;
16066   char buf[MSG_SIZ];
16067
16068   for (;;) {
16069     while (*p == ' ') p++;
16070     if (*p == NULLCHAR) return;
16071
16072     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16073     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16074     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16075     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16076     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16077     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16078     if (BoolFeature(&p, "reuse", &val, cps)) {
16079       /* Engine can disable reuse, but can't enable it if user said no */
16080       if (!val) cps->reuse = FALSE;
16081       continue;
16082     }
16083     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16084     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16085       if (gameMode == TwoMachinesPlay) {
16086         DisplayTwoMachinesTitle();
16087       } else {
16088         DisplayTitle("");
16089       }
16090       continue;
16091     }
16092     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16093     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16094     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16095     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16096     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16097     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16098     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16099     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16100     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16101     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16102     if (IntFeature(&p, "done", &val, cps)) {
16103       FeatureDone(cps, val);
16104       continue;
16105     }
16106     /* Added by Tord: */
16107     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16108     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16109     /* End of additions by Tord */
16110
16111     /* [HGM] added features: */
16112     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16113     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16114     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16115     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16116     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16117     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16118     if (StringFeature(&p, "option", buf, cps)) {
16119         if(cps->reload) continue; // we are reloading because of xreuse
16120         FREE(cps->option[cps->nrOptions].name);
16121         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16122         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16123         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16124           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16125             SendToProgram(buf, cps);
16126             continue;
16127         }
16128         if(cps->nrOptions >= MAX_OPTIONS) {
16129             cps->nrOptions--;
16130             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16131             DisplayError(buf, 0);
16132         }
16133         continue;
16134     }
16135     /* End of additions by HGM */
16136
16137     /* unknown feature: complain and skip */
16138     q = p;
16139     while (*q && *q != '=') q++;
16140     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16141     SendToProgram(buf, cps);
16142     p = q;
16143     if (*p == '=') {
16144       p++;
16145       if (*p == '\"') {
16146         p++;
16147         while (*p && *p != '\"') p++;
16148         if (*p == '\"') p++;
16149       } else {
16150         while (*p && *p != ' ') p++;
16151       }
16152     }
16153   }
16154
16155 }
16156
16157 void
16158 PeriodicUpdatesEvent (int newState)
16159 {
16160     if (newState == appData.periodicUpdates)
16161       return;
16162
16163     appData.periodicUpdates=newState;
16164
16165     /* Display type changes, so update it now */
16166 //    DisplayAnalysis();
16167
16168     /* Get the ball rolling again... */
16169     if (newState) {
16170         AnalysisPeriodicEvent(1);
16171         StartAnalysisClock();
16172     }
16173 }
16174
16175 void
16176 PonderNextMoveEvent (int newState)
16177 {
16178     if (newState == appData.ponderNextMove) return;
16179     if (gameMode == EditPosition) EditPositionDone(TRUE);
16180     if (newState) {
16181         SendToProgram("hard\n", &first);
16182         if (gameMode == TwoMachinesPlay) {
16183             SendToProgram("hard\n", &second);
16184         }
16185     } else {
16186         SendToProgram("easy\n", &first);
16187         thinkOutput[0] = NULLCHAR;
16188         if (gameMode == TwoMachinesPlay) {
16189             SendToProgram("easy\n", &second);
16190         }
16191     }
16192     appData.ponderNextMove = newState;
16193 }
16194
16195 void
16196 NewSettingEvent (int option, int *feature, char *command, int value)
16197 {
16198     char buf[MSG_SIZ];
16199
16200     if (gameMode == EditPosition) EditPositionDone(TRUE);
16201     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16202     if(feature == NULL || *feature) SendToProgram(buf, &first);
16203     if (gameMode == TwoMachinesPlay) {
16204         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16205     }
16206 }
16207
16208 void
16209 ShowThinkingEvent ()
16210 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16211 {
16212     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16213     int newState = appData.showThinking
16214         // [HGM] thinking: other features now need thinking output as well
16215         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16216
16217     if (oldState == newState) return;
16218     oldState = newState;
16219     if (gameMode == EditPosition) EditPositionDone(TRUE);
16220     if (oldState) {
16221         SendToProgram("post\n", &first);
16222         if (gameMode == TwoMachinesPlay) {
16223             SendToProgram("post\n", &second);
16224         }
16225     } else {
16226         SendToProgram("nopost\n", &first);
16227         thinkOutput[0] = NULLCHAR;
16228         if (gameMode == TwoMachinesPlay) {
16229             SendToProgram("nopost\n", &second);
16230         }
16231     }
16232 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16233 }
16234
16235 void
16236 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16237 {
16238   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16239   if (pr == NoProc) return;
16240   AskQuestion(title, question, replyPrefix, pr);
16241 }
16242
16243 void
16244 TypeInEvent (char firstChar)
16245 {
16246     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16247         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16248         gameMode == AnalyzeMode || gameMode == EditGame ||
16249         gameMode == EditPosition || gameMode == IcsExamining ||
16250         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16251         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16252                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16253                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16254         gameMode == Training) PopUpMoveDialog(firstChar);
16255 }
16256
16257 void
16258 TypeInDoneEvent (char *move)
16259 {
16260         Board board;
16261         int n, fromX, fromY, toX, toY;
16262         char promoChar;
16263         ChessMove moveType;
16264
16265         // [HGM] FENedit
16266         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16267                 EditPositionPasteFEN(move);
16268                 return;
16269         }
16270         // [HGM] movenum: allow move number to be typed in any mode
16271         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16272           ToNrEvent(2*n-1);
16273           return;
16274         }
16275         // undocumented kludge: allow command-line option to be typed in!
16276         // (potentially fatal, and does not implement the effect of the option.)
16277         // should only be used for options that are values on which future decisions will be made,
16278         // and definitely not on options that would be used during initialization.
16279         if(strstr(move, "!!! -") == move) {
16280             ParseArgsFromString(move+4);
16281             return;
16282         }
16283
16284       if (gameMode != EditGame && currentMove != forwardMostMove &&
16285         gameMode != Training) {
16286         DisplayMoveError(_("Displayed move is not current"));
16287       } else {
16288         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16289           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16290         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16291         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16292           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16293           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16294         } else {
16295           DisplayMoveError(_("Could not parse move"));
16296         }
16297       }
16298 }
16299
16300 void
16301 DisplayMove (int moveNumber)
16302 {
16303     char message[MSG_SIZ];
16304     char res[MSG_SIZ];
16305     char cpThinkOutput[MSG_SIZ];
16306
16307     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16308
16309     if (moveNumber == forwardMostMove - 1 ||
16310         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16311
16312         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16313
16314         if (strchr(cpThinkOutput, '\n')) {
16315             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16316         }
16317     } else {
16318         *cpThinkOutput = NULLCHAR;
16319     }
16320
16321     /* [AS] Hide thinking from human user */
16322     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16323         *cpThinkOutput = NULLCHAR;
16324         if( thinkOutput[0] != NULLCHAR ) {
16325             int i;
16326
16327             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16328                 cpThinkOutput[i] = '.';
16329             }
16330             cpThinkOutput[i] = NULLCHAR;
16331             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16332         }
16333     }
16334
16335     if (moveNumber == forwardMostMove - 1 &&
16336         gameInfo.resultDetails != NULL) {
16337         if (gameInfo.resultDetails[0] == NULLCHAR) {
16338           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16339         } else {
16340           snprintf(res, MSG_SIZ, " {%s} %s",
16341                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16342         }
16343     } else {
16344         res[0] = NULLCHAR;
16345     }
16346
16347     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16348         DisplayMessage(res, cpThinkOutput);
16349     } else {
16350       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16351                 WhiteOnMove(moveNumber) ? " " : ".. ",
16352                 parseList[moveNumber], res);
16353         DisplayMessage(message, cpThinkOutput);
16354     }
16355 }
16356
16357 void
16358 DisplayComment (int moveNumber, char *text)
16359 {
16360     char title[MSG_SIZ];
16361
16362     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16363       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16364     } else {
16365       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16366               WhiteOnMove(moveNumber) ? " " : ".. ",
16367               parseList[moveNumber]);
16368     }
16369     if (text != NULL && (appData.autoDisplayComment || commentUp))
16370         CommentPopUp(title, text);
16371 }
16372
16373 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16374  * might be busy thinking or pondering.  It can be omitted if your
16375  * gnuchess is configured to stop thinking immediately on any user
16376  * input.  However, that gnuchess feature depends on the FIONREAD
16377  * ioctl, which does not work properly on some flavors of Unix.
16378  */
16379 void
16380 Attention (ChessProgramState *cps)
16381 {
16382 #if ATTENTION
16383     if (!cps->useSigint) return;
16384     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16385     switch (gameMode) {
16386       case MachinePlaysWhite:
16387       case MachinePlaysBlack:
16388       case TwoMachinesPlay:
16389       case IcsPlayingWhite:
16390       case IcsPlayingBlack:
16391       case AnalyzeMode:
16392       case AnalyzeFile:
16393         /* Skip if we know it isn't thinking */
16394         if (!cps->maybeThinking) return;
16395         if (appData.debugMode)
16396           fprintf(debugFP, "Interrupting %s\n", cps->which);
16397         InterruptChildProcess(cps->pr);
16398         cps->maybeThinking = FALSE;
16399         break;
16400       default:
16401         break;
16402     }
16403 #endif /*ATTENTION*/
16404 }
16405
16406 int
16407 CheckFlags ()
16408 {
16409     if (whiteTimeRemaining <= 0) {
16410         if (!whiteFlag) {
16411             whiteFlag = TRUE;
16412             if (appData.icsActive) {
16413                 if (appData.autoCallFlag &&
16414                     gameMode == IcsPlayingBlack && !blackFlag) {
16415                   SendToICS(ics_prefix);
16416                   SendToICS("flag\n");
16417                 }
16418             } else {
16419                 if (blackFlag) {
16420                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16421                 } else {
16422                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16423                     if (appData.autoCallFlag) {
16424                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16425                         return TRUE;
16426                     }
16427                 }
16428             }
16429         }
16430     }
16431     if (blackTimeRemaining <= 0) {
16432         if (!blackFlag) {
16433             blackFlag = TRUE;
16434             if (appData.icsActive) {
16435                 if (appData.autoCallFlag &&
16436                     gameMode == IcsPlayingWhite && !whiteFlag) {
16437                   SendToICS(ics_prefix);
16438                   SendToICS("flag\n");
16439                 }
16440             } else {
16441                 if (whiteFlag) {
16442                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16443                 } else {
16444                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16445                     if (appData.autoCallFlag) {
16446                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16447                         return TRUE;
16448                     }
16449                 }
16450             }
16451         }
16452     }
16453     return FALSE;
16454 }
16455
16456 void
16457 CheckTimeControl ()
16458 {
16459     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16460         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16461
16462     /*
16463      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16464      */
16465     if ( !WhiteOnMove(forwardMostMove) ) {
16466         /* White made time control */
16467         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16468         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16469         /* [HGM] time odds: correct new time quota for time odds! */
16470                                             / WhitePlayer()->timeOdds;
16471         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16472     } else {
16473         lastBlack -= blackTimeRemaining;
16474         /* Black made time control */
16475         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16476                                             / WhitePlayer()->other->timeOdds;
16477         lastWhite = whiteTimeRemaining;
16478     }
16479 }
16480
16481 void
16482 DisplayBothClocks ()
16483 {
16484     int wom = gameMode == EditPosition ?
16485       !blackPlaysFirst : WhiteOnMove(currentMove);
16486     DisplayWhiteClock(whiteTimeRemaining, wom);
16487     DisplayBlackClock(blackTimeRemaining, !wom);
16488 }
16489
16490
16491 /* Timekeeping seems to be a portability nightmare.  I think everyone
16492    has ftime(), but I'm really not sure, so I'm including some ifdefs
16493    to use other calls if you don't.  Clocks will be less accurate if
16494    you have neither ftime nor gettimeofday.
16495 */
16496
16497 /* VS 2008 requires the #include outside of the function */
16498 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16499 #include <sys/timeb.h>
16500 #endif
16501
16502 /* Get the current time as a TimeMark */
16503 void
16504 GetTimeMark (TimeMark *tm)
16505 {
16506 #if HAVE_GETTIMEOFDAY
16507
16508     struct timeval timeVal;
16509     struct timezone timeZone;
16510
16511     gettimeofday(&timeVal, &timeZone);
16512     tm->sec = (long) timeVal.tv_sec;
16513     tm->ms = (int) (timeVal.tv_usec / 1000L);
16514
16515 #else /*!HAVE_GETTIMEOFDAY*/
16516 #if HAVE_FTIME
16517
16518 // include <sys/timeb.h> / moved to just above start of function
16519     struct timeb timeB;
16520
16521     ftime(&timeB);
16522     tm->sec = (long) timeB.time;
16523     tm->ms = (int) timeB.millitm;
16524
16525 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16526     tm->sec = (long) time(NULL);
16527     tm->ms = 0;
16528 #endif
16529 #endif
16530 }
16531
16532 /* Return the difference in milliseconds between two
16533    time marks.  We assume the difference will fit in a long!
16534 */
16535 long
16536 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16537 {
16538     return 1000L*(tm2->sec - tm1->sec) +
16539            (long) (tm2->ms - tm1->ms);
16540 }
16541
16542
16543 /*
16544  * Code to manage the game clocks.
16545  *
16546  * In tournament play, black starts the clock and then white makes a move.
16547  * We give the human user a slight advantage if he is playing white---the
16548  * clocks don't run until he makes his first move, so it takes zero time.
16549  * Also, we don't account for network lag, so we could get out of sync
16550  * with GNU Chess's clock -- but then, referees are always right.
16551  */
16552
16553 static TimeMark tickStartTM;
16554 static long intendedTickLength;
16555
16556 long
16557 NextTickLength (long timeRemaining)
16558 {
16559     long nominalTickLength, nextTickLength;
16560
16561     if (timeRemaining > 0L && timeRemaining <= 10000L)
16562       nominalTickLength = 100L;
16563     else
16564       nominalTickLength = 1000L;
16565     nextTickLength = timeRemaining % nominalTickLength;
16566     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16567
16568     return nextTickLength;
16569 }
16570
16571 /* Adjust clock one minute up or down */
16572 void
16573 AdjustClock (Boolean which, int dir)
16574 {
16575     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16576     if(which) blackTimeRemaining += 60000*dir;
16577     else      whiteTimeRemaining += 60000*dir;
16578     DisplayBothClocks();
16579     adjustedClock = TRUE;
16580 }
16581
16582 /* Stop clocks and reset to a fresh time control */
16583 void
16584 ResetClocks ()
16585 {
16586     (void) StopClockTimer();
16587     if (appData.icsActive) {
16588         whiteTimeRemaining = blackTimeRemaining = 0;
16589     } else if (searchTime) {
16590         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16591         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16592     } else { /* [HGM] correct new time quote for time odds */
16593         whiteTC = blackTC = fullTimeControlString;
16594         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16595         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16596     }
16597     if (whiteFlag || blackFlag) {
16598         DisplayTitle("");
16599         whiteFlag = blackFlag = FALSE;
16600     }
16601     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16602     DisplayBothClocks();
16603     adjustedClock = FALSE;
16604 }
16605
16606 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16607
16608 /* Decrement running clock by amount of time that has passed */
16609 void
16610 DecrementClocks ()
16611 {
16612     long timeRemaining;
16613     long lastTickLength, fudge;
16614     TimeMark now;
16615
16616     if (!appData.clockMode) return;
16617     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16618
16619     GetTimeMark(&now);
16620
16621     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16622
16623     /* Fudge if we woke up a little too soon */
16624     fudge = intendedTickLength - lastTickLength;
16625     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16626
16627     if (WhiteOnMove(forwardMostMove)) {
16628         if(whiteNPS >= 0) lastTickLength = 0;
16629         timeRemaining = whiteTimeRemaining -= lastTickLength;
16630         if(timeRemaining < 0 && !appData.icsActive) {
16631             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16632             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16633                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16634                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16635             }
16636         }
16637         DisplayWhiteClock(whiteTimeRemaining - fudge,
16638                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16639     } else {
16640         if(blackNPS >= 0) lastTickLength = 0;
16641         timeRemaining = blackTimeRemaining -= lastTickLength;
16642         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16643             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16644             if(suddenDeath) {
16645                 blackStartMove = forwardMostMove;
16646                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16647             }
16648         }
16649         DisplayBlackClock(blackTimeRemaining - fudge,
16650                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16651     }
16652     if (CheckFlags()) return;
16653
16654     if(twoBoards) { // count down secondary board's clocks as well
16655         activePartnerTime -= lastTickLength;
16656         partnerUp = 1;
16657         if(activePartner == 'W')
16658             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16659         else
16660             DisplayBlackClock(activePartnerTime, TRUE);
16661         partnerUp = 0;
16662     }
16663
16664     tickStartTM = now;
16665     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16666     StartClockTimer(intendedTickLength);
16667
16668     /* if the time remaining has fallen below the alarm threshold, sound the
16669      * alarm. if the alarm has sounded and (due to a takeback or time control
16670      * with increment) the time remaining has increased to a level above the
16671      * threshold, reset the alarm so it can sound again.
16672      */
16673
16674     if (appData.icsActive && appData.icsAlarm) {
16675
16676         /* make sure we are dealing with the user's clock */
16677         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16678                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16679            )) return;
16680
16681         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16682             alarmSounded = FALSE;
16683         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16684             PlayAlarmSound();
16685             alarmSounded = TRUE;
16686         }
16687     }
16688 }
16689
16690
16691 /* A player has just moved, so stop the previously running
16692    clock and (if in clock mode) start the other one.
16693    We redisplay both clocks in case we're in ICS mode, because
16694    ICS gives us an update to both clocks after every move.
16695    Note that this routine is called *after* forwardMostMove
16696    is updated, so the last fractional tick must be subtracted
16697    from the color that is *not* on move now.
16698 */
16699 void
16700 SwitchClocks (int newMoveNr)
16701 {
16702     long lastTickLength;
16703     TimeMark now;
16704     int flagged = FALSE;
16705
16706     GetTimeMark(&now);
16707
16708     if (StopClockTimer() && appData.clockMode) {
16709         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16710         if (!WhiteOnMove(forwardMostMove)) {
16711             if(blackNPS >= 0) lastTickLength = 0;
16712             blackTimeRemaining -= lastTickLength;
16713            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16714 //         if(pvInfoList[forwardMostMove].time == -1)
16715                  pvInfoList[forwardMostMove].time =               // use GUI time
16716                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16717         } else {
16718            if(whiteNPS >= 0) lastTickLength = 0;
16719            whiteTimeRemaining -= lastTickLength;
16720            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16721 //         if(pvInfoList[forwardMostMove].time == -1)
16722                  pvInfoList[forwardMostMove].time =
16723                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16724         }
16725         flagged = CheckFlags();
16726     }
16727     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16728     CheckTimeControl();
16729
16730     if (flagged || !appData.clockMode) return;
16731
16732     switch (gameMode) {
16733       case MachinePlaysBlack:
16734       case MachinePlaysWhite:
16735       case BeginningOfGame:
16736         if (pausing) return;
16737         break;
16738
16739       case EditGame:
16740       case PlayFromGameFile:
16741       case IcsExamining:
16742         return;
16743
16744       default:
16745         break;
16746     }
16747
16748     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16749         if(WhiteOnMove(forwardMostMove))
16750              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16751         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16752     }
16753
16754     tickStartTM = now;
16755     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16756       whiteTimeRemaining : blackTimeRemaining);
16757     StartClockTimer(intendedTickLength);
16758 }
16759
16760
16761 /* Stop both clocks */
16762 void
16763 StopClocks ()
16764 {
16765     long lastTickLength;
16766     TimeMark now;
16767
16768     if (!StopClockTimer()) return;
16769     if (!appData.clockMode) return;
16770
16771     GetTimeMark(&now);
16772
16773     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16774     if (WhiteOnMove(forwardMostMove)) {
16775         if(whiteNPS >= 0) lastTickLength = 0;
16776         whiteTimeRemaining -= lastTickLength;
16777         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16778     } else {
16779         if(blackNPS >= 0) lastTickLength = 0;
16780         blackTimeRemaining -= lastTickLength;
16781         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16782     }
16783     CheckFlags();
16784 }
16785
16786 /* Start clock of player on move.  Time may have been reset, so
16787    if clock is already running, stop and restart it. */
16788 void
16789 StartClocks ()
16790 {
16791     (void) StopClockTimer(); /* in case it was running already */
16792     DisplayBothClocks();
16793     if (CheckFlags()) return;
16794
16795     if (!appData.clockMode) return;
16796     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16797
16798     GetTimeMark(&tickStartTM);
16799     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16800       whiteTimeRemaining : blackTimeRemaining);
16801
16802    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16803     whiteNPS = blackNPS = -1;
16804     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16805        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16806         whiteNPS = first.nps;
16807     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16808        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16809         blackNPS = first.nps;
16810     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16811         whiteNPS = second.nps;
16812     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16813         blackNPS = second.nps;
16814     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16815
16816     StartClockTimer(intendedTickLength);
16817 }
16818
16819 char *
16820 TimeString (long ms)
16821 {
16822     long second, minute, hour, day;
16823     char *sign = "";
16824     static char buf[32];
16825
16826     if (ms > 0 && ms <= 9900) {
16827       /* convert milliseconds to tenths, rounding up */
16828       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16829
16830       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16831       return buf;
16832     }
16833
16834     /* convert milliseconds to seconds, rounding up */
16835     /* use floating point to avoid strangeness of integer division
16836        with negative dividends on many machines */
16837     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16838
16839     if (second < 0) {
16840         sign = "-";
16841         second = -second;
16842     }
16843
16844     day = second / (60 * 60 * 24);
16845     second = second % (60 * 60 * 24);
16846     hour = second / (60 * 60);
16847     second = second % (60 * 60);
16848     minute = second / 60;
16849     second = second % 60;
16850
16851     if (day > 0)
16852       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16853               sign, day, hour, minute, second);
16854     else if (hour > 0)
16855       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16856     else
16857       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16858
16859     return buf;
16860 }
16861
16862
16863 /*
16864  * This is necessary because some C libraries aren't ANSI C compliant yet.
16865  */
16866 char *
16867 StrStr (char *string, char *match)
16868 {
16869     int i, length;
16870
16871     length = strlen(match);
16872
16873     for (i = strlen(string) - length; i >= 0; i--, string++)
16874       if (!strncmp(match, string, length))
16875         return string;
16876
16877     return NULL;
16878 }
16879
16880 char *
16881 StrCaseStr (char *string, char *match)
16882 {
16883     int i, j, length;
16884
16885     length = strlen(match);
16886
16887     for (i = strlen(string) - length; i >= 0; i--, string++) {
16888         for (j = 0; j < length; j++) {
16889             if (ToLower(match[j]) != ToLower(string[j]))
16890               break;
16891         }
16892         if (j == length) return string;
16893     }
16894
16895     return NULL;
16896 }
16897
16898 #ifndef _amigados
16899 int
16900 StrCaseCmp (char *s1, char *s2)
16901 {
16902     char c1, c2;
16903
16904     for (;;) {
16905         c1 = ToLower(*s1++);
16906         c2 = ToLower(*s2++);
16907         if (c1 > c2) return 1;
16908         if (c1 < c2) return -1;
16909         if (c1 == NULLCHAR) return 0;
16910     }
16911 }
16912
16913
16914 int
16915 ToLower (int c)
16916 {
16917     return isupper(c) ? tolower(c) : c;
16918 }
16919
16920
16921 int
16922 ToUpper (int c)
16923 {
16924     return islower(c) ? toupper(c) : c;
16925 }
16926 #endif /* !_amigados    */
16927
16928 char *
16929 StrSave (char *s)
16930 {
16931   char *ret;
16932
16933   if ((ret = (char *) malloc(strlen(s) + 1)))
16934     {
16935       safeStrCpy(ret, s, strlen(s)+1);
16936     }
16937   return ret;
16938 }
16939
16940 char *
16941 StrSavePtr (char *s, char **savePtr)
16942 {
16943     if (*savePtr) {
16944         free(*savePtr);
16945     }
16946     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16947       safeStrCpy(*savePtr, s, strlen(s)+1);
16948     }
16949     return(*savePtr);
16950 }
16951
16952 char *
16953 PGNDate ()
16954 {
16955     time_t clock;
16956     struct tm *tm;
16957     char buf[MSG_SIZ];
16958
16959     clock = time((time_t *)NULL);
16960     tm = localtime(&clock);
16961     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16962             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16963     return StrSave(buf);
16964 }
16965
16966
16967 char *
16968 PositionToFEN (int move, char *overrideCastling)
16969 {
16970     int i, j, fromX, fromY, toX, toY;
16971     int whiteToPlay;
16972     char buf[MSG_SIZ];
16973     char *p, *q;
16974     int emptycount;
16975     ChessSquare piece;
16976
16977     whiteToPlay = (gameMode == EditPosition) ?
16978       !blackPlaysFirst : (move % 2 == 0);
16979     p = buf;
16980
16981     /* Piece placement data */
16982     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16983         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16984         emptycount = 0;
16985         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16986             if (boards[move][i][j] == EmptySquare) {
16987                 emptycount++;
16988             } else { ChessSquare piece = boards[move][i][j];
16989                 if (emptycount > 0) {
16990                     if(emptycount<10) /* [HGM] can be >= 10 */
16991                         *p++ = '0' + emptycount;
16992                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16993                     emptycount = 0;
16994                 }
16995                 if(PieceToChar(piece) == '+') {
16996                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16997                     *p++ = '+';
16998                     piece = (ChessSquare)(DEMOTED piece);
16999                 }
17000                 *p++ = PieceToChar(piece);
17001                 if(p[-1] == '~') {
17002                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17003                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17004                     *p++ = '~';
17005                 }
17006             }
17007         }
17008         if (emptycount > 0) {
17009             if(emptycount<10) /* [HGM] can be >= 10 */
17010                 *p++ = '0' + emptycount;
17011             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17012             emptycount = 0;
17013         }
17014         *p++ = '/';
17015     }
17016     *(p - 1) = ' ';
17017
17018     /* [HGM] print Crazyhouse or Shogi holdings */
17019     if( gameInfo.holdingsWidth ) {
17020         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17021         q = p;
17022         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17023             piece = boards[move][i][BOARD_WIDTH-1];
17024             if( piece != EmptySquare )
17025               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17026                   *p++ = PieceToChar(piece);
17027         }
17028         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17029             piece = boards[move][BOARD_HEIGHT-i-1][0];
17030             if( piece != EmptySquare )
17031               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17032                   *p++ = PieceToChar(piece);
17033         }
17034
17035         if( q == p ) *p++ = '-';
17036         *p++ = ']';
17037         *p++ = ' ';
17038     }
17039
17040     /* Active color */
17041     *p++ = whiteToPlay ? 'w' : 'b';
17042     *p++ = ' ';
17043
17044   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17045     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17046   } else {
17047   if(nrCastlingRights) {
17048      q = p;
17049      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17050        /* [HGM] write directly from rights */
17051            if(boards[move][CASTLING][2] != NoRights &&
17052               boards[move][CASTLING][0] != NoRights   )
17053                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17054            if(boards[move][CASTLING][2] != NoRights &&
17055               boards[move][CASTLING][1] != NoRights   )
17056                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17057            if(boards[move][CASTLING][5] != NoRights &&
17058               boards[move][CASTLING][3] != NoRights   )
17059                 *p++ = boards[move][CASTLING][3] + AAA;
17060            if(boards[move][CASTLING][5] != NoRights &&
17061               boards[move][CASTLING][4] != NoRights   )
17062                 *p++ = boards[move][CASTLING][4] + AAA;
17063      } else {
17064
17065         /* [HGM] write true castling rights */
17066         if( nrCastlingRights == 6 ) {
17067             int q, k=0;
17068             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17069                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17070             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17071                  boards[move][CASTLING][2] != NoRights  );
17072             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17073                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17074                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17075                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17076                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17077             }
17078             if(q) *p++ = 'Q';
17079             k = 0;
17080             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17081                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17082             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17083                  boards[move][CASTLING][5] != NoRights  );
17084             if(gameInfo.variant == VariantSChess) {
17085                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17086                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17087                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17088                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17089             }
17090             if(q) *p++ = 'q';
17091         }
17092      }
17093      if (q == p) *p++ = '-'; /* No castling rights */
17094      *p++ = ' ';
17095   }
17096
17097   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17098      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17099     /* En passant target square */
17100     if (move > backwardMostMove) {
17101         fromX = moveList[move - 1][0] - AAA;
17102         fromY = moveList[move - 1][1] - ONE;
17103         toX = moveList[move - 1][2] - AAA;
17104         toY = moveList[move - 1][3] - ONE;
17105         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17106             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17107             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17108             fromX == toX) {
17109             /* 2-square pawn move just happened */
17110             *p++ = toX + AAA;
17111             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17112         } else {
17113             *p++ = '-';
17114         }
17115     } else if(move == backwardMostMove) {
17116         // [HGM] perhaps we should always do it like this, and forget the above?
17117         if((signed char)boards[move][EP_STATUS] >= 0) {
17118             *p++ = boards[move][EP_STATUS] + AAA;
17119             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17120         } else {
17121             *p++ = '-';
17122         }
17123     } else {
17124         *p++ = '-';
17125     }
17126     *p++ = ' ';
17127   }
17128   }
17129
17130     /* [HGM] find reversible plies */
17131     {   int i = 0, j=move;
17132
17133         if (appData.debugMode) { int k;
17134             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17135             for(k=backwardMostMove; k<=forwardMostMove; k++)
17136                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17137
17138         }
17139
17140         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17141         if( j == backwardMostMove ) i += initialRulePlies;
17142         sprintf(p, "%d ", i);
17143         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17144     }
17145     /* Fullmove number */
17146     sprintf(p, "%d", (move / 2) + 1);
17147
17148     return StrSave(buf);
17149 }
17150
17151 Boolean
17152 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17153 {
17154     int i, j;
17155     char *p, c;
17156     int emptycount, virgin[BOARD_FILES];
17157     ChessSquare piece;
17158
17159     p = fen;
17160
17161     /* [HGM] by default clear Crazyhouse holdings, if present */
17162     if(gameInfo.holdingsWidth) {
17163        for(i=0; i<BOARD_HEIGHT; i++) {
17164            board[i][0]             = EmptySquare; /* black holdings */
17165            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17166            board[i][1]             = (ChessSquare) 0; /* black counts */
17167            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17168        }
17169     }
17170
17171     /* Piece placement data */
17172     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17173         j = 0;
17174         for (;;) {
17175             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17176                 if (*p == '/') p++;
17177                 emptycount = gameInfo.boardWidth - j;
17178                 while (emptycount--)
17179                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17180                 break;
17181 #if(BOARD_FILES >= 10)
17182             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17183                 p++; emptycount=10;
17184                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17185                 while (emptycount--)
17186                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17187 #endif
17188             } else if (isdigit(*p)) {
17189                 emptycount = *p++ - '0';
17190                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17191                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17192                 while (emptycount--)
17193                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17194             } else if (*p == '+' || isalpha(*p)) {
17195                 if (j >= gameInfo.boardWidth) return FALSE;
17196                 if(*p=='+') {
17197                     piece = CharToPiece(*++p);
17198                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17199                     piece = (ChessSquare) (PROMOTED piece ); p++;
17200                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17201                 } else piece = CharToPiece(*p++);
17202
17203                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17204                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17205                     piece = (ChessSquare) (PROMOTED piece);
17206                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17207                     p++;
17208                 }
17209                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17210             } else {
17211                 return FALSE;
17212             }
17213         }
17214     }
17215     while (*p == '/' || *p == ' ') p++;
17216
17217     /* [HGM] look for Crazyhouse holdings here */
17218     while(*p==' ') p++;
17219     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17220         if(*p == '[') p++;
17221         if(*p == '-' ) p++; /* empty holdings */ else {
17222             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17223             /* if we would allow FEN reading to set board size, we would   */
17224             /* have to add holdings and shift the board read so far here   */
17225             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17226                 p++;
17227                 if((int) piece >= (int) BlackPawn ) {
17228                     i = (int)piece - (int)BlackPawn;
17229                     i = PieceToNumber((ChessSquare)i);
17230                     if( i >= gameInfo.holdingsSize ) return FALSE;
17231                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17232                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17233                 } else {
17234                     i = (int)piece - (int)WhitePawn;
17235                     i = PieceToNumber((ChessSquare)i);
17236                     if( i >= gameInfo.holdingsSize ) return FALSE;
17237                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17238                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17239                 }
17240             }
17241         }
17242         if(*p == ']') p++;
17243     }
17244
17245     while(*p == ' ') p++;
17246
17247     /* Active color */
17248     c = *p++;
17249     if(appData.colorNickNames) {
17250       if( c == appData.colorNickNames[0] ) c = 'w'; else
17251       if( c == appData.colorNickNames[1] ) c = 'b';
17252     }
17253     switch (c) {
17254       case 'w':
17255         *blackPlaysFirst = FALSE;
17256         break;
17257       case 'b':
17258         *blackPlaysFirst = TRUE;
17259         break;
17260       default:
17261         return FALSE;
17262     }
17263
17264     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17265     /* return the extra info in global variiables             */
17266
17267     /* set defaults in case FEN is incomplete */
17268     board[EP_STATUS] = EP_UNKNOWN;
17269     for(i=0; i<nrCastlingRights; i++ ) {
17270         board[CASTLING][i] =
17271             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17272     }   /* assume possible unless obviously impossible */
17273     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17274     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17275     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17276                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17277     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17278     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17279     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17280                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17281     FENrulePlies = 0;
17282
17283     while(*p==' ') p++;
17284     if(nrCastlingRights) {
17285       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17286       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17287           /* castling indicator present, so default becomes no castlings */
17288           for(i=0; i<nrCastlingRights; i++ ) {
17289                  board[CASTLING][i] = NoRights;
17290           }
17291       }
17292       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17293              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17294              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17295              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17296         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17297
17298         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17299             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17300             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17301         }
17302         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17303             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17304         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17305                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17306         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17307                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17308         switch(c) {
17309           case'K':
17310               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17311               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17312               board[CASTLING][2] = whiteKingFile;
17313               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17314               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17315               break;
17316           case'Q':
17317               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17318               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17319               board[CASTLING][2] = whiteKingFile;
17320               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17321               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17322               break;
17323           case'k':
17324               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17325               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17326               board[CASTLING][5] = blackKingFile;
17327               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17328               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17329               break;
17330           case'q':
17331               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17332               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17333               board[CASTLING][5] = blackKingFile;
17334               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17335               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17336           case '-':
17337               break;
17338           default: /* FRC castlings */
17339               if(c >= 'a') { /* black rights */
17340                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17341                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17342                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17343                   if(i == BOARD_RGHT) break;
17344                   board[CASTLING][5] = i;
17345                   c -= AAA;
17346                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17347                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17348                   if(c > i)
17349                       board[CASTLING][3] = c;
17350                   else
17351                       board[CASTLING][4] = c;
17352               } else { /* white rights */
17353                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17354                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17355                     if(board[0][i] == WhiteKing) break;
17356                   if(i == BOARD_RGHT) break;
17357                   board[CASTLING][2] = i;
17358                   c -= AAA - 'a' + 'A';
17359                   if(board[0][c] >= WhiteKing) break;
17360                   if(c > i)
17361                       board[CASTLING][0] = c;
17362                   else
17363                       board[CASTLING][1] = c;
17364               }
17365         }
17366       }
17367       for(i=0; i<nrCastlingRights; i++)
17368         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17369       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17370     if (appData.debugMode) {
17371         fprintf(debugFP, "FEN castling rights:");
17372         for(i=0; i<nrCastlingRights; i++)
17373         fprintf(debugFP, " %d", board[CASTLING][i]);
17374         fprintf(debugFP, "\n");
17375     }
17376
17377       while(*p==' ') p++;
17378     }
17379
17380     /* read e.p. field in games that know e.p. capture */
17381     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17382        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17383       if(*p=='-') {
17384         p++; board[EP_STATUS] = EP_NONE;
17385       } else {
17386          char c = *p++ - AAA;
17387
17388          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17389          if(*p >= '0' && *p <='9') p++;
17390          board[EP_STATUS] = c;
17391       }
17392     }
17393
17394
17395     if(sscanf(p, "%d", &i) == 1) {
17396         FENrulePlies = i; /* 50-move ply counter */
17397         /* (The move number is still ignored)    */
17398     }
17399
17400     return TRUE;
17401 }
17402
17403 void
17404 EditPositionPasteFEN (char *fen)
17405 {
17406   if (fen != NULL) {
17407     Board initial_position;
17408
17409     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17410       DisplayError(_("Bad FEN position in clipboard"), 0);
17411       return ;
17412     } else {
17413       int savedBlackPlaysFirst = blackPlaysFirst;
17414       EditPositionEvent();
17415       blackPlaysFirst = savedBlackPlaysFirst;
17416       CopyBoard(boards[0], initial_position);
17417       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17418       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17419       DisplayBothClocks();
17420       DrawPosition(FALSE, boards[currentMove]);
17421     }
17422   }
17423 }
17424
17425 static char cseq[12] = "\\   ";
17426
17427 Boolean
17428 set_cont_sequence (char *new_seq)
17429 {
17430     int len;
17431     Boolean ret;
17432
17433     // handle bad attempts to set the sequence
17434         if (!new_seq)
17435                 return 0; // acceptable error - no debug
17436
17437     len = strlen(new_seq);
17438     ret = (len > 0) && (len < sizeof(cseq));
17439     if (ret)
17440       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17441     else if (appData.debugMode)
17442       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17443     return ret;
17444 }
17445
17446 /*
17447     reformat a source message so words don't cross the width boundary.  internal
17448     newlines are not removed.  returns the wrapped size (no null character unless
17449     included in source message).  If dest is NULL, only calculate the size required
17450     for the dest buffer.  lp argument indicats line position upon entry, and it's
17451     passed back upon exit.
17452 */
17453 int
17454 wrap (char *dest, char *src, int count, int width, int *lp)
17455 {
17456     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17457
17458     cseq_len = strlen(cseq);
17459     old_line = line = *lp;
17460     ansi = len = clen = 0;
17461
17462     for (i=0; i < count; i++)
17463     {
17464         if (src[i] == '\033')
17465             ansi = 1;
17466
17467         // if we hit the width, back up
17468         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17469         {
17470             // store i & len in case the word is too long
17471             old_i = i, old_len = len;
17472
17473             // find the end of the last word
17474             while (i && src[i] != ' ' && src[i] != '\n')
17475             {
17476                 i--;
17477                 len--;
17478             }
17479
17480             // word too long?  restore i & len before splitting it
17481             if ((old_i-i+clen) >= width)
17482             {
17483                 i = old_i;
17484                 len = old_len;
17485             }
17486
17487             // extra space?
17488             if (i && src[i-1] == ' ')
17489                 len--;
17490
17491             if (src[i] != ' ' && src[i] != '\n')
17492             {
17493                 i--;
17494                 if (len)
17495                     len--;
17496             }
17497
17498             // now append the newline and continuation sequence
17499             if (dest)
17500                 dest[len] = '\n';
17501             len++;
17502             if (dest)
17503                 strncpy(dest+len, cseq, cseq_len);
17504             len += cseq_len;
17505             line = cseq_len;
17506             clen = cseq_len;
17507             continue;
17508         }
17509
17510         if (dest)
17511             dest[len] = src[i];
17512         len++;
17513         if (!ansi)
17514             line++;
17515         if (src[i] == '\n')
17516             line = 0;
17517         if (src[i] == 'm')
17518             ansi = 0;
17519     }
17520     if (dest && appData.debugMode)
17521     {
17522         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17523             count, width, line, len, *lp);
17524         show_bytes(debugFP, src, count);
17525         fprintf(debugFP, "\ndest: ");
17526         show_bytes(debugFP, dest, len);
17527         fprintf(debugFP, "\n");
17528     }
17529     *lp = dest ? line : old_line;
17530
17531     return len;
17532 }
17533
17534 // [HGM] vari: routines for shelving variations
17535 Boolean modeRestore = FALSE;
17536
17537 void
17538 PushInner (int firstMove, int lastMove)
17539 {
17540         int i, j, nrMoves = lastMove - firstMove;
17541
17542         // push current tail of game on stack
17543         savedResult[storedGames] = gameInfo.result;
17544         savedDetails[storedGames] = gameInfo.resultDetails;
17545         gameInfo.resultDetails = NULL;
17546         savedFirst[storedGames] = firstMove;
17547         savedLast [storedGames] = lastMove;
17548         savedFramePtr[storedGames] = framePtr;
17549         framePtr -= nrMoves; // reserve space for the boards
17550         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17551             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17552             for(j=0; j<MOVE_LEN; j++)
17553                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17554             for(j=0; j<2*MOVE_LEN; j++)
17555                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17556             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17557             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17558             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17559             pvInfoList[firstMove+i-1].depth = 0;
17560             commentList[framePtr+i] = commentList[firstMove+i];
17561             commentList[firstMove+i] = NULL;
17562         }
17563
17564         storedGames++;
17565         forwardMostMove = firstMove; // truncate game so we can start variation
17566 }
17567
17568 void
17569 PushTail (int firstMove, int lastMove)
17570 {
17571         if(appData.icsActive) { // only in local mode
17572                 forwardMostMove = currentMove; // mimic old ICS behavior
17573                 return;
17574         }
17575         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17576
17577         PushInner(firstMove, lastMove);
17578         if(storedGames == 1) GreyRevert(FALSE);
17579         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17580 }
17581
17582 void
17583 PopInner (Boolean annotate)
17584 {
17585         int i, j, nrMoves;
17586         char buf[8000], moveBuf[20];
17587
17588         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17589         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17590         nrMoves = savedLast[storedGames] - currentMove;
17591         if(annotate) {
17592                 int cnt = 10;
17593                 if(!WhiteOnMove(currentMove))
17594                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17595                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17596                 for(i=currentMove; i<forwardMostMove; i++) {
17597                         if(WhiteOnMove(i))
17598                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17599                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17600                         strcat(buf, moveBuf);
17601                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17602                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17603                 }
17604                 strcat(buf, ")");
17605         }
17606         for(i=1; i<=nrMoves; i++) { // copy last variation back
17607             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17608             for(j=0; j<MOVE_LEN; j++)
17609                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17610             for(j=0; j<2*MOVE_LEN; j++)
17611                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17612             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17613             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17614             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17615             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17616             commentList[currentMove+i] = commentList[framePtr+i];
17617             commentList[framePtr+i] = NULL;
17618         }
17619         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17620         framePtr = savedFramePtr[storedGames];
17621         gameInfo.result = savedResult[storedGames];
17622         if(gameInfo.resultDetails != NULL) {
17623             free(gameInfo.resultDetails);
17624       }
17625         gameInfo.resultDetails = savedDetails[storedGames];
17626         forwardMostMove = currentMove + nrMoves;
17627 }
17628
17629 Boolean
17630 PopTail (Boolean annotate)
17631 {
17632         if(appData.icsActive) return FALSE; // only in local mode
17633         if(!storedGames) return FALSE; // sanity
17634         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17635
17636         PopInner(annotate);
17637         if(currentMove < forwardMostMove) ForwardEvent(); else
17638         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17639
17640         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17641         return TRUE;
17642 }
17643
17644 void
17645 CleanupTail ()
17646 {       // remove all shelved variations
17647         int i;
17648         for(i=0; i<storedGames; i++) {
17649             if(savedDetails[i])
17650                 free(savedDetails[i]);
17651             savedDetails[i] = NULL;
17652         }
17653         for(i=framePtr; i<MAX_MOVES; i++) {
17654                 if(commentList[i]) free(commentList[i]);
17655                 commentList[i] = NULL;
17656         }
17657         framePtr = MAX_MOVES-1;
17658         storedGames = 0;
17659 }
17660
17661 void
17662 LoadVariation (int index, char *text)
17663 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17664         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17665         int level = 0, move;
17666
17667         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17668         // first find outermost bracketing variation
17669         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17670             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17671                 if(*p == '{') wait = '}'; else
17672                 if(*p == '[') wait = ']'; else
17673                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17674                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17675             }
17676             if(*p == wait) wait = NULLCHAR; // closing ]} found
17677             p++;
17678         }
17679         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17680         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17681         end[1] = NULLCHAR; // clip off comment beyond variation
17682         ToNrEvent(currentMove-1);
17683         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17684         // kludge: use ParsePV() to append variation to game
17685         move = currentMove;
17686         ParsePV(start, TRUE, TRUE);
17687         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17688         ClearPremoveHighlights();
17689         CommentPopDown();
17690         ToNrEvent(currentMove+1);
17691 }
17692
17693 void
17694 LoadTheme ()
17695 {
17696     char *p, *q, buf[MSG_SIZ];
17697     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17698         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17699         ParseArgsFromString(buf);
17700         ActivateTheme(TRUE); // also redo colors
17701         return;
17702     }
17703     p = nickName;
17704     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17705     {
17706         int len;
17707         q = appData.themeNames;
17708         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17709       if(appData.useBitmaps) {
17710         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17711                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17712                 appData.liteBackTextureMode,
17713                 appData.darkBackTextureMode );
17714       } else {
17715         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17716                 Col2Text(2),   // lightSquareColor
17717                 Col2Text(3) ); // darkSquareColor
17718       }
17719       if(appData.useBorder) {
17720         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17721                 appData.border);
17722       } else {
17723         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17724       }
17725       if(appData.useFont) {
17726         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17727                 appData.renderPiecesWithFont,
17728                 appData.fontToPieceTable,
17729                 Col2Text(9),    // appData.fontBackColorWhite
17730                 Col2Text(10) ); // appData.fontForeColorBlack
17731       } else {
17732         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17733                 appData.pieceDirectory);
17734         if(!appData.pieceDirectory[0])
17735           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17736                 Col2Text(0),   // whitePieceColor
17737                 Col2Text(1) ); // blackPieceColor
17738       }
17739       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17740                 Col2Text(4),   // highlightSquareColor
17741                 Col2Text(5) ); // premoveHighlightColor
17742         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17743         if(insert != q) insert[-1] = NULLCHAR;
17744         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17745         if(q)   free(q);
17746     }
17747     ActivateTheme(FALSE);
17748 }